Eduard Lebedyuk · Feb 11, 2022 go to post

Does foreground mode work only for windows with cterm/iristerm client or can any telnet client connect?

Eduard Lebedyuk · Feb 10, 2022 go to post

From the docs:

  • <STRINGSTACK> An expression is too long, there are too many expressions in an argument for a single command, or an expression contains many very long strings. Simplify the expression.
  • <MAXSTRING> There has been an attempt to specify or create a data string longer than the implementation allows. The maximum string size is 3,641,144 characters. Attempting to concatenate strings that would result in a string exceeding this maximum string size results in a <MAXSTRING> error.

MAXSTRING is always related to maximum length of 3,641,144 characters for one string (assuming long strings are enabled). STRINGSTACK can be raised in several different circumstances, including a lot of small strings, recursion and so on.

Increasing bbsiz may help avoid STRINGSTACK error, but not MAXSTRING.

In your case use streams by replacing:

set file=object.%Get(i).file

with:

set file=object.%Get(i).%Get("file",,"stream")
Eduard Lebedyuk · Feb 7, 2022 go to post

The issue is it would actually hang the process for 10 seconds. MakeTimerCall implementation would not - BP could process other messages in the meantime.

Eduard Lebedyuk · Feb 7, 2022 go to post

Check this answer if you need a CSV file.

If you need some custom format:

// prepare statement
set st =  ##class(%SQL.Statement).%New()
set st.%SelectMode = 1 // ODBC
set sc = st.%Prepare(query)
quit:$$$ISERR(sc) sc

// execute statement
#dim result As %SQL.StatementResult
set result = st.%Execute()
quit:result.%SQLCODE'=0 $$$ERROR($$$SQLError, result.%SQLCODE, result.%Message)

// iterate metadata (for example if you need a header)
#dim metadata As SQL.StatementMetadata
set metadata = result.%GetMetadata()
set columnCount = metadata.columns.Count()

for i=1:1:columnCount {
	#dim column As %SQL.StatementColumn
	set column = metadata.columns.GetAt(i)
}

// iterate results
while result.%Next() {
	for i=1:1:columnCount {
		set value = result.%GetData(i)
	}
}
Eduard Lebedyuk · Feb 5, 2022 go to post

"list of" will support this too in 2022.1

If you define Addresses list property like this:

Property Addresses As list Of Sample.Address(SQLPROJECTION = "table/column", STORAGEDEFAULT = "array");

Child table would be created.

Eduard Lebedyuk · Feb 4, 2022 go to post

Yes, looked promising but there are issues as we cant iterate positive/negative: 1,-1,2,-2 and so on.

Eduard Lebedyuk · Feb 2, 2022 go to post

And why starting with  -9 ?

Considering we need to hit anything from -9 to 9 where else should I start?

Eduard Lebedyuk · Feb 2, 2022 go to post

can't figure out how to check if the child process (in $zchild) is still running.  

Append 2 commands to your main command.

First one, execute before your main command to create a file with a name equal to process id.

Second one, execute after your main command. It deletes the file.

In your IRIS process check if the file exists.

Ugly but it works.

Eduard Lebedyuk · Feb 2, 2022 go to post

Can't reproduce. Can you post an example please?

Maybe you have a semicolon after your query?

Eduard Lebedyuk · Feb 2, 2022 go to post

49 as we can iterate numbers, not list elements:

ClassMethod Solve(o As %String) As %Integer
{
 s y=$lfs(o) f i=-9:1 ret:$lf(y,i)&&'$lf(y,-i) i
}
Eduard Lebedyuk · Feb 2, 2022 go to post

Clever!

If only there was a way to get "remaining parts" length, in that case we could divide the sum you've got by a number of repetitions and get the answer.

Eduard Lebedyuk · Jan 28, 2022 go to post

1. If it's a one-off thing redefine your property setter:

Class Test.RO Extends %Persistent
{

Property RecordCreatedTime As %TimeStamp [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];

Method RecordCreatedTimeSet(value As %TimeStamp) As %Status
{
	if i%RecordCreatedTime="" {
		set i%RecordCreatedTime=value
	}
	quit $$$OK
}

/// do ##class(Test.RO).test()
ClassMethod test()
{
	set obj = ..%New()
	write obj.RecordCreatedTime,!
	
	set obj.RecordCreatedTime = "2000-01-01 00:00:01"
	write obj.RecordCreatedTime,!
}
}

It's also automatically set during object creation courtesy of InitialExpression, you can remove it if you want to set the value yourself.

2. If you have the same immutable property or a set of immutable properties you can write an abstract class:

Class Test.Base [Abstract]
{

Property RecordCreatedTime As %TimeStamp [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];

Method RecordCreatedTimeSet(value As %TimeStamp) As %Status
{
	if i%RecordCreatedTime="" {
		set i%RecordCreatedTime=value
	}
	quit $$$OK
}
}

And add it to inheritance whenever you need:

Class Test.RO Extends (%Persistent, Test.Base)
{
}

3. Finally if you have a lot of immutable properties and they are all different you'll need a custom datatype. Custom datatype defines method generators for getters, setters and all other property methods. Let's inherit from %String so we only need to redefine a setter:

Class Test.ROString Extends %String
{

/// Generate Setter
Method Set(%val) [ CodeMode = objectgenerator, NoContext ]
{
	quit:%mode'="propertymethod" $$$OK
	do %code.WriteLine($c(9) _ "if i%" _ $g(%member) _ "="""" {")
	do %code.WriteLine($c(9,9) _ "set i%" _ $g(%member) _ "=%val")
	do %code.WriteLine($c(9) _ "}")
	do %code.WriteLine($c(9) _ "quit $$$OK")
	quit $$$OK
}

}

Now we create a property of Test.ROString type:

Class Test.RO Extends %Persistent
{
Property RecordCreatedTime As Test.ROString [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];
}

And it would be immutable. In fact if we check a generated setter:

zRecordCreatedTimeSet(%val) public {
    if i%RecordCreatedTime="" {
        set i%RecordCreatedTime=%val
    }
    quit 1 }

It would look quite similar to what I have wrote in (1), only automatically generated.

Eduard Lebedyuk · Jan 28, 2022 go to post

Great idea!

I'd only add check to prevent rewrite of a stream on every save unless the dynamic object was modified:

Method %OnAddToSaveSet(depth As %Integer = 3, insert As %Integer = 0, callcount As %Integer = 0) As %Status [ Private, ServerOnly = 1 ]
{
   do:m%json ..jstr.Clear(), ..json.%ToJSON(..jstr)
   Quit $$$OK
}
Eduard Lebedyuk · Jan 28, 2022 go to post

You can't call native api methods which write to device as is.

If you need to call some piece of code which writes to device use this wrapper:

/// Executes and returns device output 
/// pObj - OREF or class
/// pMethod - instance or class method to execute respectively
/// pArgs - additional arguments
ClassMethod OutputToStr(pObj, pMethod, pArgs...) As %String [ ProcedureBlock = 0 ]
{
	set tOldIORedirected = ##class(%Device).ReDirectIO()
	set tOldMnemonic = ##class(%Device).GetMnemonicRoutine()
	set tOldIO = $io
	try {
		set str=""

		//Redirect IO to the current routine - makes use of the labels defined below
		use $io::("^"_$ZNAME)

		//Enable redirection
		do ##class(%Device).ReDirectIO(1)

		if $isobject(pObj) {
			do $Method(pObj,pMethod,pArgs...)
		} elseif $$$comClassDefined(pObj) {
			do $ClassMethod(pObj,pMethod,pArgs...)
		}
	} catch ex {
		set str = ""
	}

	//Return to original redirection/mnemonic routine settings
	if (tOldMnemonic '= "") {
		use tOldIO::("^"_tOldMnemonic)
	} else {
		use tOldIO
	}
	do ##class(%Device).ReDirectIO(tOldIORedirected)

	quit str

	//Labels that allow for IO redirection
	//Read Character - we don't care about reading
rchr(c)      quit
	//Read a string - we don't care about reading
rstr(sz,to)  quit
	//Write a character - call the output label
wchr(s)      do output($char(s))  quit
	//Write a form feed - call the output label
wff()        do output($char(12))  quit
	//Write a newline - call the output label
wnl()        do output($char(13,10))  quit
	//Write a string - call the output label
wstr(s)      do output(s)  quit
	//Write a tab - call the output label
wtab(s)      do output($char(9))  quit
	//Output label - this is where you would handle what you actually want to do.
	//  in our case, we want to write to str
output(s)    set str=str_s   quit
}

So in your case it would be something like:

IRISObject data  = (IRISObject) iris.classMethodObject(CACHE_CLASS_NAME, method, _args);
String string = iris.classMethodString("SomeClass", "OutputToStr", data, "ToJSON")
data.close();
Eduard Lebedyuk · Jan 27, 2022 go to post

If you check Ens.Util.Log class where logs are stored, you'll notice that Text property is limited to 32 000 characters, so logging anything larger than that is impossible.

There are several approaches you can take:

  • Logging to files as described by @Jeffrey.Drumm. I'd add that you can use %File:TempFilename to obtain a random but valid filename to write to. Alternatively use session id and timestamp to create the filename, with each session having a separate folder.
  • Logging to streams. Use %Stream.GblChrCompress to save on space. JSON is very compressible.
  • Use a debugging business operation. Create a business operation which accepts everything and either defers or just does nothing. Send a copy of your stream there. This way you get immediate access to a content from the Visual Trace.
Eduard Lebedyuk · Jan 27, 2022 go to post

Assuming you need a detached signature:

gpg --local-user [fingerprint] --sign --armor --output somefile.tar.xz.asc --detach-sig somefile.tar.xz

Copied from Stack.