Eduard Lebedyuk · Jul 14, 2017 go to post

When you define a query:

Query MyQuery() As %Query
{
  QUERY TEXT
}

Or

Query MyQuery() As %SQLQuery
{
  QUERY TEXT
}

That means that this query implements an interface provided by %Library.Query or %Library.SQLQuery respectively. They define method generators for Fetch/Execute/Func methods (more on them). Each method of the interface class gets called during compilation

You can subclass %Library.Query or %Library.SQLQuery to define your own methods. Here's custom query interface that does 2 things:

  • Changes ROWSPEC to Id123:%String
    • It can be seen in class definition after compilation
    • <QueryName>GetInfo and <QueryName>GetODBCInfo methods also reflect new ROWSPEC properly
  • Generates <QueryName>GetText method that returns query text
Class Utils.MyQuery Extends %SQLQuery
{

/// This method would be ran before others and changes ROWSPEC to "Id123:%String"
ClassMethod %ChangeRowspec() [ CodeMode = objectgenerator, ServerOnly = 1 ]
{
    // quit if we're not compiling a query
    if %mode="method" quit $$$OK
    
    set class = %class.Name
    set query = %compiledmethod.parent.Name        
    set rowspec = "Id123:%String"
    
    // Modify query definition
    $$$defSubMemberSet(class,$$$cCLASSquery,query,$$$cQUERYparameter,"ROWSPEC",rowspec)
    
    // Modify compiled query definition
    $$$comSubMemberSet(class,$$$cCLASSquery,query,$$$cQUERYparameter,"ROWSPEC",rowspec)
    
    // Update compile-time parameter value
    set %parameter("ROWSPEC") = rowspec
    
    // Update class definition
    do UpdClsDef^%occLibrary(class)
    
    quit $$$OK
}

/// GetText is a method that is used to get query text as a %String
ClassMethod GetText() As %String [ CodeMode = objectgenerator, ServerOnly = 1 ]
{
    if %mode="method" quit $$$OK
    $$$comMemberKeyGetLvar(query,%classname,$$$cCLASSquery,%property,$$$cQUERYsqlquery)
    do %code.WriteLine("    quit " _ $$$quote(query))
    quit $$$OK
}

}

And here's a test class with our new query:

Class Utils.MyQueryTest
{
Query ABC() As Utils.MyQuery
{
SELECT 1
}
}

After Utils.MyQueryTest is compiled it looks like this:

Class Utils.MyQueryTest
{
Query ABC() As Utils.MyQuery(ROWSPEC = "Id123:%String")
{
SELECT 1
}
}

You can modify this interface to get any behavior you want.

Code is available on GitHub.

Eduard Lebedyuk · Jul 12, 2017 go to post

What about it?

It says that class methods can be jobbed as ..Method() instead of ##class(Package.Class).Method(), if we're in a class context.

Eduard Lebedyuk · Jul 12, 2017 go to post

Please consider clarifying your question.

InterSystems Caché is a database, so adding 200 fields is fairly straightforward.

InterSystems Ensemble is a ESB. It has Business Hosts (Business services, Business processes, Business operations). They manipulate data stored in Caché database based on arbitrary rules.

Eduard Lebedyuk · Jul 12, 2017 go to post

Only class methods can be used for jobs, otherwise where does the object belong - to a parent or child process?

Eduard Lebedyuk · Jul 12, 2017 go to post

what is ExtentFunc?

For each persistent class there is an Extent class query that returns IDs.

For Sample.Employee class it is:

SELECT ID, Name, SSN, Home_City, Home_State FROM Sample.Employee

For each class query, <QueryName>Func  method gets generated.

You can see it in the class int code using  Show Other View (Open Sample.Employee and press Ctrl+Shift+V).

Here's the generated <QueryName>Func  method for the Extent query of the Sample.Employee class:

zExtentFunc() public {
    try {
        set tSchemaPath = ##class(%SQL.Statement).%ClassPath($classname())
            set tStatement = ##class(%SQL.Statement).%New(,tSchemaPath)
            do tStatement.prepare(" SELECT ID , Name , SSN , Home_City , Home_State FROM Sample . Employee")
        set tResult = tStatement.%Execute()
    }
    catch tException { if '$Isobject($Get(tResult)) { set tResult = ##class(%SQL.StatementResult).%New() } set tResult.%SQLCODE=tException.AsSQLCODE(),tResult.%Message=tException.AsSQLMessage() }
    Quit tResult }

It executes the query and returns result set. More on class queries.

 

class %sqlcq.SAMPLES.cls9

To see the code:

  • Go to General SQL settings in SMP and set Cached Query - Save Source to Yes.
  • Purge cached queries from sample namespace.
  • Execute this query again.
  • Check the new query class name- probably  %sqlcq.SAMPLES.cls1
  • It now could be seen in studio
Eduard Lebedyuk · Jul 11, 2017 go to post

Ensemble runs under other user, so:

1. Compare environment variables from terminal and Ensemble (path is especially important):

do $zf(-1,"set > vars.txt")

2. Check that ensemble can access all required files.

3. Check working directory.

4. (Optional) Provide full paths to all files and binaries.

Eduard Lebedyuk · Jul 11, 2017 go to post

Sample.Company and Sample.Employee share one company/many employees relationship.

Do you want to iterate over employees and display a company name for each?

set rs = ##class(Sample.Employee).ExtentFunc()
while rs.%Next() { set emp = ##class(Sample.Employee).%OpenId(rs.ID) w emp.Company.Name,! }

You can even get company names even without opening objects:

set rs = ##class(Sample.Employee).ExtentFunc()
while rs.%Next() { w ##class(Sample.Company).NameGetStored(##class(Sample.Employee).CompanyGetStored(rs.ID)),! }
Eduard Lebedyuk · Jul 11, 2017 go to post

Thank you!

Here's a method to get OS environment variable:

write $System.Util.GetEnviron(VariableName)
Eduard Lebedyuk · Jul 11, 2017 go to post

So far NodeJS is only used as a general dev environment and build pipeline manager.

Maybe front-end would communicate with Caché backend via REST API? Or websockets?

Eduard Lebedyuk · Jul 11, 2017 go to post

You can convert binary openssl output to base64 and write that to file:

cmd=$$$FormatText("openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 -sign %1 %2 | base64 > %3",fileKey,fileMsg,file64)

base64 is available in most linux flavors, and on Windows in various GNU CoreUtils builds and in Git (usually under C:\Program Files\Git\usr\bin\).

 

Also in an a business operation filenames should be generated randomly (probably via ##clss(%File).TempFilename(ext)) to avoid conflicts.

Eduard Lebedyuk · Jul 11, 2017 go to post

Export relevant globals to xml file(1 file per namespace) and compare hash sums. Also there's a lot of tools to display XML diffs if hash sums would differ.

Otherwise it's per global iteration and compare. I'd write some method to calculate global hash maybe (complete iteration and hash of keys and value). So:

  • Get lists of globals in both ns
  • Iterate over lists, and calculate hashes for each global separately
  • Compare lists (iterate over one list and delete each same value from both lists, all that's left is diff)
  • Report if global is in one list and not in the other
  • Report if global hashes differ across lists
  • (Optional) Display detailed compare for globals with different hashes
Eduard Lebedyuk · Jul 11, 2017 go to post

Please note that $$$TAB macro is actually a whitespace:

ClassMethod GetTab() [ CodeMode = expression ]
{
$$$TAB
}

Call to GetTab returns:

USER>zzdump ##class(Utils.Persistent).GetTab()
0000: 20

I usually use either $c(9) or user-defined macro for tabulation.

Eduard Lebedyuk · Jul 10, 2017 go to post

You'll need a Business Service and a Business Operation.

Business service runs every X seconds (or you can configure a more complicated schedule) gets a list of hosts and calls Business Operation. Business Operation performs the request.

Eduard Lebedyuk · Jul 9, 2017 go to post
do ##class(%Compiler.UDL.TextServices).GetTextAsString(namespace, class, .text)
write text
Eduard Lebedyuk · Jun 29, 2017 go to post

Make plaintext property triggered computed on insert/update rather than always computed.

Property  PlainText As %String(MAXLEN="") [ SqlComputeCode = {set {*} = ##class(%iKnow.Source.Converter.Html).StripTags({HtmlText}}, SqlComputed, SqlComputeOnChange = (%%INSERT, %%UPDATE) ];
Eduard Lebedyuk · Jun 27, 2017 go to post

There's no need to search for the /s1/s2/s3 in the second template, as the first template would send every node into the second template. So your XSLT should probably look like this:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="xml" indent="yes"/>
     
    <xsl:template match="//@* | //node()">
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates select="node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="//s3" >
        <xsl:copy>
            <xsl:apply-templates select="@*"/>
            Content Replaced
        </xsl:copy>
    </xsl:template>
    
</xsl:stylesheet>
Eduard Lebedyuk · Jun 27, 2017 go to post

As both Angular and Zen manage page state, combining them it does not seem particularly promising.

Eduard Lebedyuk · Jun 26, 2017 go to post

Thank you, Benjamin.

I thought TRANSFORMATIONSPEC parameter could have been used somehow, but I'll try your suggested approach.

Eduard Lebedyuk · Jun 26, 2017 go to post

Are these (Caché and Ensemble) systems in production or development?

Emsemble is Caché + Some classes. So the easiest solution would be to move Caché application into Ensemble instance.

Example of calling Ensemble from Caché is available in Demo.ZenService.Zen.WeatherReportForm class, GetWeatherReport method in ENSDEMO namespace.

Eduard Lebedyuk · Jun 25, 2017 go to post

Install latest version (we're currently on v4) from here. Installation instructions are there too (just download and import, that's all).