Eduard Lebedyuk · Jul 7, 2016 go to post

This problem can be reproduced on all infinite scroll pages. The particular page I reported is just an example.

Eduard Lebedyuk · Jul 7, 2016 go to post

Sure did. To clarify, it's the fastest way available by default. The fastest way is a direct global reference (provided of course that we do not have the object in a context already available), but we need to either hardcode the global reference or calculate it at compile time. Here's the test class (I won't copy it here - it's fairly long). The results are like this:

Iterations: 10000
Object access: .130111
GetStored access: .014388
SQL access: .020268
Global access: .007717
Object access takes 904.30% of GetStored time
Object access takes 641.95% of SQL time
Object access takes 1686.03% of Global time
GetStored access takes 70.99% of SQL time
GetStored access takes 186.45% of Global time
SQL access takes 262.64% of Global time

Where:

  • Object access is opening an object and retrieving the property
  • SQL access is embedded SQL
  • GetStored is a method discussed in this article
  • Global is a direct global reference to a stored property

One piece of code I'd like to share here is the macro to get global reference for a property by a class and property name:

Class Utils.GetStored Extends %Persistent
{

Property text As %String;

ClassMethod Global() As %Status
{
    #Define GetStoredProp(%cls,%prop,%idvar) ##Expression(##class(Utils.GetStored).GetPropGLVN(%cls,%prop, $name(%idvar)))
    Set Id = $Random(999)+1
    Set Val = $$$GetStoredProp("Utils.GetStored","text", Id)
}

/// Write ##class(Utils.GetStored).GetPropGLVN("Utils.GetStored", "text")
ClassMethod GetPropGLVN(Class, Property, IdVar = "id") As %String
{
    Set StoredCode = $Get($$$EXTPROPondisk($$$pEXT,Class,Property))
    Set StoredCode = $Replace(StoredCode, "(id)", "(" _ IdVar _ ")")
    Return StoredCode
}

On compilation the string with $$$GetStoredProp macro would be compiled into:

Set Val = $listget($g(^Utils.GetStoredD(Id)),2) 
Eduard Lebedyuk · Jul 6, 2016 go to post

"val" should be in Upper case I suppose?

No.  IndexOpen calls IndexExists to get object ID.  In IndexExists "val" is matched to corresponding ID with the following SQL expression (except for IDKEYExists. It calls %ExistsId):

SELECT %ID INTO :id 
FROM Package.Class 
WHERE 
  (val IS NOT NULL AND IndexProperty = val) OR 
  (val IS NULL     AND IndexProperty IS NULL)

The interesting question would be - why not traverse index global to get id instead of using SQL?

Eduard Lebedyuk · Jul 1, 2016 go to post

Cube has 2 methods:

  • %OnGetFilterSpec  - callback method which executes for every query and gives a Cube a chance to programmatically define a filter spec
  • %KillCache - deletes all cached values for the cube

If you combine them like this, you can get what you want:

ClassMethod %OnGetFilterSpec(pFilterSpec As %String) As %String
{
    Do  ..%KillCache()
    Quit pFilterSpec
}

Note, that %OnGetFilterSpec   has an access to %query object which is of %DeepSee.Query.query  class. I tried to set  some of it params but it didn't seem to help:

Set %query.useCache = $$$NO
Set %query.%mustCompute = $$$YES

You can try to modify %query some other way, or you can try set a query error global node

Set $$$DeepSeeResultsGLVN(%cubeIndex, %query.%QueryKey,"error") = "whatever"

It forces the query to recompute the results.

I think it's definitely doable, but you need to tinker with either %query or query  global cache. Well, it depends on the purpose: if it's low traffic/dev system killing all the cube cache is ok, but you may want to kill the cache only  for a current query.

Eduard Lebedyuk · Jun 29, 2016 go to post

I mean can you post it (or any other base64 string giving you an error) here?

I seem unable to reproduce the error.

Eduard Lebedyuk · Jun 29, 2016 go to post

Based on Alexanders comment, I think this should work.

<form method="post" action="">
<table>
<tr><td><textarea rows="40" cols="200" name="submitstring"></textarea></td></tr>
<tr><td><select name="decodeoption"><option>Decode</option><option>Encode</option></select><input type="submit"/></td></tr>
<tr><td>&nbsp;</td></tr>
<tr><td><h2>Result</h2></td></tr>
<tr><td>

<script language=Cache runat=server>
Set tString = $Get(%request.Data("submitstring",1))
Set tAction = $Get(%request.Data("decodeoption",1))
If tAction = "Decode" {
    Set tString = $SYSTEM.Encryption.Base64Decode(tString)
    Set tOutput = $ZCONVERT(tString,"I","UTF8")
} Else {
    Set tString = $ZCONVERT(tString,"O","UTF8")
    Set tOutput = $SYSTEM.Encryption.Base64Encode(tString)
}
Write tOutput
</script>
</td></tr>
</table>
</form>

I added  UTF8 conversion:

Set tOutput = $ZCONVERT(tString,"I","UTF8")

and

 Set tString = $ZCONVERT(tString,"O","UTF8")
Eduard Lebedyuk · Jun 28, 2016 go to post

It may be useful as one of the metrics related to the code quality/monitoring.

For example, my continuous integration system Cache GitHub CI tracks compile time for each commit, and if it suddenly grows, then the offending commit may be worth looking into.  But if we add one other metric - "lines of code added", then some of the offending commits may be removed based on a now determinable fact that a raise in compilation time is caused by a project size increase.

On the screenshot: compilation time (Length column) for some commits in a real-life project.

 

Other usage - find classes longer than, for example, 500 sloc and separate them into several classes.

Eduard Lebedyuk · Jun 28, 2016 go to post

Thanks.

In regards to the documentation, I'd like to clarify that it refers to a database page accessible at

SMP → System Operation → Databases, and not SMP → Menu → Configure Databases.

Eduard Lebedyuk · Jun 24, 2016 go to post

Triggers are, unfortunately, not an option as MySQL table gets updated via backup (so it's DROP TABLE -> CREATE TABLE -> INSERT)

Eduard Lebedyuk · Jun 22, 2016 go to post

OnStart gets executed before queries, see Ens.Director class, StartProduction method.

Or you can execute EnableConfigItem on an item of a disabled production.

Eduard Lebedyuk · Jun 21, 2016 go to post

You can close the cursor and open it again:

ClassMethod Test()
{
     &sql(  DECLARE C1 CURSOR FOR
            SELECT TOP 10 ID
            INTO :id
            FROM Sample.Person
    )

    &sql(OPEN C1)
    &sql(FETCH C1)
    
    Set first = $$$YES

    While (SQLCODE = 0) {
        Write id,!       
        &sql(FETCH C1)
        If id=9 && first {
            Set first = $$$NO
                &sql(CLOSE C1)
                &sql(OPEN C1)
                &sql(FETCH C1)
        }
    }

    &sql(CLOSE C1)
}
Eduard Lebedyuk · Jun 21, 2016 go to post

Zen Mojo is a set of classes that enable you to create web pages, suitable for either mobile devices or desktops.

ZEN Mojo is a new and improved ZEN.

Eduard Lebedyuk · Jun 21, 2016 go to post

If you are familiar (proficent) with ZEN use ZEN Mojo.

If not, write HTML/JS/CSS client and REST server.

That said, DeepSee would definitely be an easier solution to implement and more robust overall.

Eduard Lebedyuk · Jun 21, 2016 go to post

Use DeepSee - Intersystems analytics technology. It can be embedded in transactional systems to enable users to make decisions based on real-time analysis of structured and unstructured data.

There's a sample Patients cube in samples namespace.

Here's how it can look for end user.

Eduard Lebedyuk · Jun 20, 2016 go to post

Replace full reference to a  global with one zn.

 Consider the following code:

Class Utils.Global
{

/// Do ##class(Utils.Global).Generate()
ClassMethod Generate(NS = "SAMPLES", Count = 100000000)
{
    New $namespace
    Zn NS
    Kill ^LAB
    For i=1:1:Count {
        Set ^LAB(i) = i
    }
}

/// Do ##class(Utils.Global).Test()
ClassMethod Test(NS = "SAMPLES")
{
    Set time = $p($h,",",2)
    Do ..Ref1(NS)
    Set time1 = $p($h,",",2)
    Do ..Ref2(NS)
    Set time2 = $p($h,",",2)
    Write "Full ref time ",time1-time,!,"ZN time ",time2-time1
}

ClassMethod Ref1(NS)
{
    Set PIDX=""    
    For {
        Set PIDX=$ORDER(^[NS]LAB(PIDX))
        Quit:PIDX=""  
    }
}

ClassMethod Ref2(NS)
{
    New $namespace
    Zn NS
    Set PIDX=""    
    For {
        Set PIDX=$ORDER(^LAB(PIDX))
        Quit:PIDX=""  
    }
}

}

When I run Do ##class(Utils.Global).Test() in a terminal I get the following results:

Full ref time 38
ZN time 35

even better difference on smaller loops (10000000):

Full ref time 3
ZN time 2

GitHub.

Eduard Lebedyuk · Jun 18, 2016 go to post

Use EnableConfigItem method of Ens.Director class to enable/disable business hosts programmatically.

It can possibly be called from OnStart method in production class, from where you can determine an environment (DEV or PROD) and enable/disable correct business hosts based on that.

Eduard Lebedyuk · Jun 18, 2016 go to post

HTTP debug proxies can and do work with https traffic, for example Charles. You need to install the certificate for that, but it's definitely doable.

Eduard Lebedyuk · Jun 16, 2016 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.

Eduard Lebedyuk · Jun 16, 2016 go to post

If data is too big for a string then result would also be too big for a string.

 I think something like this would work:

  1. Create result stream
  2. Find positions of the beginning and the end of the first placeholder in the template
  3. Write template from the start to the beginning of the first placeholder
  4. Copy data stream into  result stream
  5. Repeat 2-4 till you have no more placeholders (if there's more than one placeholder per template)
  6. Send result stream out via a soap webservice

It can be done as one method with this signature ( so it would be possible to pass any number of data streams for one template):

ClassMethod FillTemplate(Template As %String, Data... As %Stream.GlobalCharacter) As %Stream.GlobalCharacter {}
Eduard Lebedyuk · Jun 14, 2016 go to post

Check if variable is defined beforehand and generate/use a new variable name if required:

ClassMethod GetNewVarName() As %String
{
    Set Name = "Temp"
    While $Data(@Name) {
        Set Name = Name _ $Random(100)
    }
    Return Name
}
Eduard Lebedyuk · Jun 13, 2016 go to post

The  easy way to display list object:

for i=1:1:user.Roles.Count() w user.Roles.GetAt(i),!

You can also serialize list object to $list and display it:

zw user.Roles.Serialize()

Also note that lists of datatypes defined as class properties are of %Collection.ListOfDT class (which is somewhat similar but not identical to %ListOfDataTypes class).

You can also get all information on %ListOfDataTypes objects with zw command:

s b=##class(%ListOfDataTypes).%New()
do b.Insert(1)
do b.Insert(2)
do b.Insert(3)
zw b
Eduard Lebedyuk · Jun 12, 2016 go to post

Great article, I find this feature very useful too, here's my %ZLANGC00:

 ; %ZLANGC00
 ; custom commands for ObjectScript
 ; http://localhost:57772/csp/docbook/DocBook.UI.Page.cls?KEY=GSTU_customize
  Quit    

/// Execute Query and display the results
/// Call like this:
/// zsql "SELECT TOP 10 Name FROM Sample.Person"
ZSQL(Query)
    #Dim ResultSet As %SQL.StatementResult
    Set ResultSet = ##class(%SQL.Statement).%ExecDirect(, Query)
    Do ResultSet.%Display()
    Quit

/// Move user to a namespace of his choice
/// Call like this:
/// zm "s"
ZM(Namespace)
    Do MoveToNamespace^%ZSTART(Namespace)
    Quit
    
/// Move to Samples namespace and set a as a Samples.Person object
/// Set b as %ZEN.proxyObject
/// Set c as %Object
ZO(move = 1) Public
    ZN:move "SAMPLES"
    If ##class(%Dictionary.CompiledClass).%ExistsId("Sample.Person") {
        Set p = ##class(Sample.Person).%OpenId(1)
    }
    Set a = ##class(%ZEN.proxyObject).%New()
    Set b = {}
    Quit

and related %ZSTART:

%ZSTART() {
    Quit    
}

/// This entry point is invoked on user login
/// offering user to choose a namespace
LOGIN() Public {
    Set Timeout = 3
    Write "Namespace <" _ $Namespace _ ">: "  
    Read Namespace:Timeout // Get value of a Namespace variable
    Quit:Namespace=""
    Do MoveToNamespace(Namespace)
}    

/// Does actual moving to a chosen namespace
/// This is an entry point, for cases where
/// Namespace value is already aquired
MoveToNamespace(Namespace = "") Public {
    Set Timeout = 3
    #Dim List As %ListOfDataTypes
    Set List = $$GetNamespaceList(Namespace)
    Set Count = List.Count()
    
    If Count = 1 {
        Set Choice = 1
    } ElseIf Count > 1 {
        Do DisplayList(List)

        // If there is less then 10 results, then we need only 1 digit
        // Otherwise we need 2 digits
        // It is assumed that no more then 99 results would be returned
        Read "Select number <1>: ", Choice#$Select(Count < 10:1, 1:2):Timeout

        // If the user entered nothing or not a valid number
        // we select first namespace in a list to go to
        Set:((Choice = "") || ('$IsValidNum(Choice, 0, 1, Count))) Choice = 1
    } Else {
        // No namespaces found
        Quit
    }
    
    Zn List.GetAt(Choice)
}

/// Get all availible namespaces that satisfy
/// "Name %STARTSWITH Namespace" condition
/// as %ListOfDataTypes
GetNamespaceList(Namespace = "") {
    New $Namespace
    Set $Namespace = "%SYS"

    #Dim List As %ListOfDataTypes
    #Dim ResultSet As %SQL.StatementResult
    Set List = ##class(%ListOfDataTypes).%New()

    Set UserCondition = "%UPPER(Name) %STARTSWITH %UPPER(?)"  // Or [ if you wish
    Set Condition="(" _ UserCondition _ ") AND (SectionHeader='Namespaces') AND (%UPPER(Name)!='%ALL')"
    Set SQL = "SELECT Name FROM Config.Namespaces WHERE " _ Condition

    Set ResultSet = ##class(%SQL.Statement).%ExecDirect(, SQL, Namespace)
    While ResultSet.%Next() {
        Do List.Insert(ResultSet.%Get("Name"))
    }

    Quit List
}

/// Display %ListOfDataTypes in a format:
/// 1    item
/// 2    item
/// ...
DisplayList(List) {
    #Dim List As %ListOfDataTypes
    Write !
    For i = 1:1:List.Count() {
        Write i, $C(9), List.GetAt(i), !
    }
}

Github repo.

Eduard Lebedyuk · Jun 10, 2016 go to post

Both BuildValueArray and LogicalToDisplay work with serialized form of %ListOfDataTypes object - $list string:

do ##class(%ListOfDataTypes).BuildValueArray($lb("a","b","c"), .out)
zw out

Outputs:

out(1)="a"
out(2)="b"
out(3)="c"

To sum up:

  • %ListOfDataTypes - object
  • serialized %ListOfDataTypes  - $list
  • $list - a string (with special properties, but not an object)
  • %List - $list