Eduard Lebedyuk · Jan 20, 2023 go to post

I get a syntax error when I do:

Are you doing this in console/terminal? Macros are not available there.

To check if you got an error in a terminal execute:

if sc=1 {w "OK"} else {w $system.Status.DisplayError(sc)}
Eduard Lebedyuk · Jan 20, 2023 go to post

Assuming you have a sync mirror established, adding new db to mirror is as simple as:

  1. Create DB on Primary.
  2. Run SYS.Mirror:AddDatabase. It returns %Status, check that it's ok with $$$ISOK(sc). It should be equal to 1.
  3. Dismount database on Primary (using SYS.Database:DismountDatabase) OR Freeze IRIS (Backup.General:ExternalFreeze).
  4. Copy IRIS.DAT to Backup.
  5. Mount database on Primary (using SYS.Database:MountDatabase) OR Thaw IRIS (Backup.General:ExternalThaw).
  6. Mount database on Backup.
  7. Activate database on Backup (SYS.Mirror:ActivateMirroredDatabase).
  8. Catchup database on Backup (SYS.Mirror:CatchupDB).

Please note that some methods accept db name, most accept db directory, and others db sfn. Please keep that in mind.

Eduard Lebedyuk · Jan 19, 2023 go to post

Eduard, Is the  rate at which one BS can process known or is it variable based on data unit to be processed?

It is variable based on data unit to be processed. Data unit (file) size varies between 1Kb and 100Mb.

Similarly is the rate of arrival known or possible to detect?

IRIS BS pulls messages, so as soon as BS job is done with the message, next message is pulled from the external queue (AWS SQS).

Is the design to have all the processing of a data unit in the BS rather than pass to a BP/BO?

Yes, it's a stateless app, so I need to process message and report success/error immediately since container can be reprovisioned at any time.

Eduard Lebedyuk · Jan 19, 2023 go to post

CPU utilization would likely depend on the number of workers, wouldn't it? 

It sure does. Question is how to scale workers to optimize queue consumption

Eduard Lebedyuk · Jan 19, 2023 go to post

Go to the SMP -> System Administration -> Configuration -> National Language Settings -> Locale Definitions and compare selected locales.

Eduard Lebedyuk · Jan 18, 2023 go to post

Try: ##class(%Studio.Project).InstallFromFile( "/display=none /displaylog=0 /displayerror=0")

Eduard Lebedyuk · Jan 13, 2023 go to post

Yes, like that.

Is it the same OS?

You can also try running:

openssl s_client -connect <URL or IP>:<port>
Eduard Lebedyuk · Jan 13, 2023 go to post

Would it work with Enabled cipherlist (TLSv1.2 and below): ALL

Can you set Maximum Protocol Version to TLSv1.3?

Eduard Lebedyuk · Jan 13, 2023 go to post

As process spawning is expensive, parallel queries are processed using Work Queue Manager. It works by having a queue managing process spawn worker processes at the system startup. When a new parallel query needs to be executed, it is distributed to workers.

That is why the $zparent value is not what you expected. It is a value of a work queue managing process.

You can use it instead, as it's relatively constant.

Before executing your main query, run this query to get Work Queue Manager JobId:

Class Test.Parallel
{

Query Test() As %SQLQuery
{
SELECT Test.Parallel_Parent() UNION %PARALLEL
SELECT Test.Parallel_Parent()
}

ClassMethod Parent() As %Integer [ CodeMode = expression, SqlProc ]
{
$zparent
}

/// do ##class(Test.Parallel).Try()
ClassMethod Try()
{
	set rs = ..TestFunc()
	do rs.%Next()
	write "Work Queue Manager Job: ", rs.%GetData(1)
}

}

Then, store your data subscribed by Work Queue Manager JobId, and all Work Queue Workers can pick it up using $zparent.

Eduard Lebedyuk · Jan 12, 2023 go to post

Parameter values are static values* and  the same for all objects.

Property values are different for each object.

Small example:

Class Circle Extends %RegisteredObject {
    
Parameter PI = 3.14;

Property Radius;

Method GetArea()
{
    quit ..#PI * ..Radius * ..Radius
}

ClassMethod Test()
{
    set circle = ..%New()
    set circle.Radius = 25
    write circle.GetArea()
}
}

* parameters can be calculated but not set by user.

Eduard Lebedyuk · Jan 12, 2023 go to post

Checking every 5 seconds should be good enough I think (with 10 being a default timeout in several places).

This is actually for a PEX adapter but it looks like a fast check.

Eduard Lebedyuk · Jan 11, 2023 go to post

Database is a physical file containing globals.

Namespace is a logical combination of one or more databases.

By default Namespace has two databases:

  • GLOBALS - default location for globals
  • ROUTINES - default location for code (classes/routines/includes)

In addition to that, namespace can have any amount of mappings. Mappings map specified globals/code from a specified database.

When you try to access a global, first global mappings are searched for this global, if no mappings are found, the GLOBALS database is used.

When you try to access some code, first package/routine mappings are searched for this code, if no mappings are found, the ROUTINES database is used.

To split data innto a separate DB:

  1. Create target DB.
  2. Make source DB RO (by forcefully logging users out for example and/or marking it as RO).
  3. Copy data/code from the source to target DB (for globals use ^GBLOCKCOPY).
  4. Create mapping from the target DB to your namespace.
  5. Confirm that data is present.
  6. Delete data from a source DB.
Eduard Lebedyuk · Jan 11, 2023 go to post

scheduled task that run every 5 mins  

Assuming task is scheduled like this:

it would be scheduled to run again 5 minutes after the completion of a last task execution, so there's no issue with several copies of a task running in parallel.

Eduard Lebedyuk · Jan 10, 2023 go to post

I would not recommend regexp for that. If you have one place with such a date, you can use transient/calculated property pair:

Class User.JSON Extends (%RegisteredObject, %JSON.Adaptor)
{

Property tsjson As %String(%JSONFIELDNAME = "ts") [ Transient ];

Property ts As %TimeStamp(%JSONINCLUDE = "none") [ SqlComputeCode = {set {*}=$replace({tsjson}," ", "T")_"Z"}, SqlComputed ];

/// d ##class(User.JSON).Test()
ClassMethod Test()
{
	set json = {"ts":"2022-02-02 01:01:34"}
	set obj = ..%New()
	zw obj.%JSONImport(json)
	w "ts:" _ obj.ts
}

}

If you have a lot of json properties,  use a custom datatype to do automatic conversion:

Class User.JSONTS Extends %Library.TimeStamp
{

ClassMethod IsValidDT(%val As %RawString) As %Status
{
    /// replace it with a real check
    q $$$OK
}

/// Converts the Objectscript value to the JSON number value.
ClassMethod JSONToLogical(%val As %Decimal) As %String [ CodeMode = generator, ServerOnly = 1 ]
{
    /// $replace({tsjson}," ", "T")_"Z"
    If 1,($$$getClassType(%class)=$$$cCLASSCLASSTYPEDATATYPE) || $$$comMemberKeyGet(%class,$$$cCLASSparameter,"%JSONENABLED",$$$cPARAMdefault) {
        Set %codemode=$$$cMETHCODEMODEEXPRESSION
        Set %code="$replace(%val,"" "", ""T"")_""Z"""
    } Else {
        Set %code=0 
    }
    Quit $$$OK
}

}

And use it instead of the standard timestamp:

Class User.JSON Extends (%RegisteredObject, %JSON.Adaptor)
{

Property ts As User.JSONTS;

/// d ##class(User.JSON).Test()
ClassMethod Test()
{
	set json = {"ts":"2022-02-02 01:01:34"}
	set obj = ..%New()
	zw obj.%JSONImport(json)
	w "ts:" _ obj.ts
}

}
Eduard Lebedyuk · Jan 9, 2023 go to post

You can do it like this:

1. Get active production:

w ##class(EnsPortal.Utils).GetCurrentProductionName()

2. Get a list of all BSes in an active production:

SELECT i.Name
FROM Ens_Config.Item i
JOIN %Dictionary.ClassDefinition_SubclassOf('Ens.BusinessService') c ON c.Name = i.ClassName
WHERE Production = ?

3. Disable all BSes (info)

for stop = 1, 0 {
  for i=1:1:$ll(bhList) {
    set host = $lg(bhList, i)
    set sc = ##class(Ens.Director).TempStopConfigItem(host, stop, 0)
  }
  set sc = ##class(Ens.Director).UpdateProduction()
}

4. Wait for all queues to empty:

SELECT TOP 1 1 AS "Processing"
FROM Ens.Queue_Enumerate()
WHERE "Count">0

5. Check that there are no active async BPs (extent size of all BPs must be 0 - here's an example)

6. Stop the production.

w ##class(Ens.Director).StopProduction()

After that and assuming deferred sending is not used (docs) it would be guaranteed that there are no in-flight messages.

Eduard Lebedyuk · Jan 7, 2023 go to post

What do you mean empty queues? Stop accepting new messages and process all messages before shutting down interoperability?

Eduard Lebedyuk · Jan 5, 2023 go to post

Globals are cached in global buffer and you can use that.

  1. Create a new database CACHED with a distinct block size (16, 32, or 64 Kb).
  2. In your global buffer settings, set the global buffer for that block size to be equal to the amount of memory you want to allocate to the cache.
  3. Map cache global into a CACHED database.

This will give you an in-memory LRU cache. If you also follow @Dmitry Maslennikov's suggestion and use PPG, nothing would be persisted. Otherwise you'll need to invalidate the persisted cache manually/by a task.

Eduard Lebedyuk · Jan 5, 2023 go to post

In a third party application, there's usually a driver configuration page, you need to add IRIS driver there. That is done by one of two ways:

  • Providing a path to IRIS jdbc jar
  • Placing IRIS jdbc jar in a special folder for all jdbc jars.
Eduard Lebedyuk · Jan 4, 2023 go to post

Here's the code I use (by @Dmitry Zasypkin):

/// Canonicalize XML.
/// in: XML string or stream to canonicalize.
/// out: Canonicalized XML is returned in this argument. If it's a string, out must be passed by refrence.
/// elementId: attrubute Id to canonicalize. If elementId="", the entire document would be canonicalized.
/// prefixList: a local of namespace=prefix pairs to add to a root tag, only in a case of exclusive canonicalization.
ClassMethod canonicalize(in As %Stream.Object, ByRef out As %Stream.Object, isInclusive As %Boolean = {$$$NO}, keepWhitespace = {$$$YES}, elementId As %String = "", ByRef prefixList As %String = "", writer As %XML.Writer = {##class(%XML.Writer).%New()}) As %Status
{
	#dim sc As %Status = $$$OK
	
	#dim importHandler As %XML.Document = ##class(%XML.Document).%New()
	set importHandler.KeepWhitespace = keepWhitespace
	
	if $isObject(in)
	{
		set sc = ##class(%XML.SAX.Parser).ParseStream(in, importHandler,, $$$SAXFULLDEFAULT-$$$SAXVALIDATIONSCHEMA)
	}
	else
	{
		set sc = ##class(%XML.SAX.Parser).ParseString(in, importHandler,, $$$SAXFULLDEFAULT-$$$SAXVALIDATIONSCHEMA)
	}
	if $$$ISERR(sc) quit sc
	
	if $isObject(in) && $isObject($get(out)) && (in = out) do in.Clear()
	
	if $isObject($get(out))
	{
		set sc = writer.OutputToStream(out)
	}
	else
	{
		set sc = writer.OutputToString()
	}
	if $$$ISERR(sc) quit sc
	
	#dim node As %XML.Node = importHandler.GetDocumentElement()
	if (elementId '= "") set node = importHandler.GetNode(importHandler.GetNodeById(elementId))

	// Main part
	if isInclusive
	{
		set sc = writer.Canonicalize(node, "c14n")
	}
	else
	{
		if (+$data(prefixList) >= 10)
		{
			#dim prefix As %String = ""
			for 
			{
				set prefix = $order(prefixList(prefix))
				if (prefix = "") quit
				do writer.AddNamespace(prefixList(prefix), prefix)
			}	
		}

		set sc = writer.Canonicalize(node)
	}
	if $$$ISERR(sc) quit sc
	
	if '$isObject($get(out))
	{
		set out = writer.GetXMLString(.sc)
		if $$$ISERR(sc) quit sc
	}
	
	do writer.Reset()
	
	quit $$$OK
}
Eduard Lebedyuk · Jan 3, 2023 go to post

If you have a web application /csp/SomeApp and users need to login to access this application, it is enough to go to a /csp/SomeApp web application configuration page and set Serve Files to Use InterSystems Security to get the effect you want. After making this change, users would not be able to access /csp/SomeApp/image.png without logging into your application first.