Ok ... so this actually works:

ClassMethod GetHL7Msg(pId As %String) As %Stream.GlobalCharacter [ SqlName = GetMsg, SqlProc ]
{
    Set tHl7 = ##class(EnsLib.HL7.Message).%OpenId(pId,,.tSC)
    Throw:$$$ISERR(tSC) ..GetErr(-400, "HL7 Message with ID "_pId_" Not Found.")
    Set tMsg = ##class(%Stream.GlobalCharacter).%New()
    Set tSC = tHl7.OutputToLibraryStream(.tMsg)
    Do tHl7.%Close()
    If tSC Set tSC=tMsg.%Save()
    Throw:$$$ISERR(tSC) ..GetErr(-400, $system.Status.GetErrorText(tSC))
    Return tMsg."%%OID"
}

I'm concerned that I'm using a persisted object. Does it automatically get killed once I leave the scope of the function (I'm assuming not, see the last paragraph)? Is there a way to force it to temp storage?

It's also very slow, but 1) I'm currently connecting to the database over a VPN connection, and 2) the average message size is around 30KB, with some messages up to 3MB. It took 5 minutes to return 1000 rows.

Looks like ^CacheStream is currently using almost 600MB.

No errors running the query from $system.SQL.Shell(), but I get the stream OREF rather than the message in the query result:

ID      SourceConfigName        TargetConfigName        Message
191344  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191348  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191352  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191356  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191360  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191364  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191368  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter
191372  InEpicMdm_Router        OutOptumMdm     25@%Stream.GlobalCharacter
191376  InEpicMdm_Router        OutOptumMdm     9@%Stream.GlobalCharacter
191380  InEpicMdm_Router        OutOptumMdm     23@%Stream.GlobalCharacter

ODBC seems to be doing a little magic in the background to return the stream data, but it's not good magic at the moment.

EDIT: Turns out the ODBC wrangler needs a persistent object's "%%OID" rather than an OREF to fetch the stream. See the eventual working method here.

Hi Eduard,

I'm having a bit of trouble applying your solution to my problem. I have only one method in EnsLib.HL7.Message that seems to populate a %Stream.TmpCharacter data type, and it doesn't allow me to "chunk" the data. In the code I posted, I should be returning a %Stream.TmpCharacter object functionally identical to yours. I think I am, but suspect that the ODBC handler in IRIS isn't finding the stream.

Also, I should add that although I had said that I wasn't seeing any errors on the client side (aside from not getting the entire result set), that's not exactly true. No errors were displayed in the client application's GUI, but it was logging them to its log file. In examining that log file I found what appears to be a stream-related entry in the connection string: StreamPrefetch=0. I can't find any documentation on it other than it being mentioned (without a description) in the Using .NET and the ADO.NET Managed Provider with Cache section. I'm assuming it takes a number of bytes(?) as an argument, since all the other boolean entries take True/False as values ...

Hi Robert,

Same result ... a single row and the same error in the IRIS xDBC log:

2020-10-24 01:35:59 [SQLCODE: <-412>:<General stream error>] [Error: <<INVALID OREF>PrepareStream+6^%SYS.SQLStreamSRV>] [Location: <ReadStreamODBC::PrepareStream>] [Client info: <Username: Jeff, Node Name: WIN10X64-VM01, IP Address: 10.208.8.90, Executable Name: HL7Spy.exe, Internal Function: AS>] [%protocol: <59>] $Id: //adhocs-iris/2020.1.0.217.1/HICG_LLC_001/kernel/common/src/aclass.c#1 $ 21256 11

 The method works fine if I define the return type as %String, but only until the query fetches a row with a message larger than MAXSTRING in size. I of course have to Return tMsg.Read() to get the output. When it hits a message that's too large, I get a <MAXSTRING> error in the IRIS xDBC log, but no error is set for the ODBC (actually ADO) client.

Hi Nora! Long time laugh

Something like this should do the trick, assuming the file to be appended to is named "spoo.txt" and the file from which you're appending is named "fleem.txt":

    Set tOut = ##class(%File).%New()
    Set tIn = ##class(%File).%New()
    Set tSC = 1
    Set tOut.Name = "spoo.txt"
    Set tIn.Name = "fleem.txt"
    Set sc = tOut.Open("WA")
    Set:$$$ISERR(sc) tSC=$$$ADDSC(tSC,sc)
    Set sc = tIn.Open()
    Set:$$$ISERR(sc) tSC=$$$ADDSC(tSC,sc)
    Quit:$$$ISERR(tSC) tSC
    While 'tIn.AtEnd
    {
        Set sc=tOut.Write(tIn.Read())
        Return:$$$ISERR(sc) sc
    }
    Do tIn.Close()
    Do tOut.Close()
    Return $$$OK

I'm not finding any significant differences in the supporting classes between HS and I4H 2020.1. You might want to open an incident with the WRC to see why you're successful in I4H and not HS.

I'm also thinking that you might be better off developing your NACK logic in a BPL, though. Routing Rules aren't really designed for this sort of thing.

I guess that depends on what you want to do if you get an invalid date. Here's a method you can add to the class I referenced above:

ClassMethod IsValidBirthDate(pDate As %String) As %Boolean
{
    Try {
            Set tHDate = $ZDATEH(pDate)
    } Catch ex {
        Return 0
    }
    Return 1
}

And you'd use it something like this:

Here's a method that, once created and compiled, will appear in your dropdown list of functions in the rule editor:

Class User.Util.DateTime Extends Ens.Rule.FunctionSet
{

ClassMethod DaysPrior(pDays As %Integer) As %String
{
    Return $ZDATE($H - pDays,8)
}

}

You'd use it in your rule like this:

Caveat: The birthdate is assumed to be valid and 8 characters in length. If there's a possibility that you would get an invalid or missing date, the ">" comparison will not be valid.

I'm pretty sure that System Default Settings would only solve this issue if you were already using it to "default" the original value for those interfaces. Had you done that, making the update would be a very simple change of one entry in SDS.

@Craig.Regester's solution is probably the easiest to implement quickly, but it might also be an opportunity to move to an SDS-based configuration.

GetAdapterSettingValue() may give you what you want. The Type parameter isn't needed.

Set archiveFilePath=##class(Ens.Director).GetAdapterSettingValue("ReadPDFFileService","ArchivePath",.status)
 

There's a corresponding GetHostSettingValue() method for non-adapter settings that works similarly.

Both seem to supply the System Default Setting when the setting is unvalued in the production.