Question
· Jul 25, 2016

How can we use %Stream.Object as an abstract definition of a morphable object ?

Let's say that we wish to store the documents sent to us via all of our Customers, and that the documents could be in PDF, spreadsheet, RTF, plain text, Comma-Separated-Values and XML.

It would be hoped that %Stream.Object would be a good choice of property to hold these documents, especially as you would expect %Stream.Object to morph between BINARY (for PDF, spreadsheet, RTF) or CHARACTER (for plain text, CSV, XML) depending on which type of stream was being persisted.

One might expect that .CopyFrom(pOriginalDocumentStream) would work without further coding, although I have not been able to make that work.

The following class definition demonstrates the issues arising, and it would be nice to know how to change this to make it work as expected (morphable stream type content).

/// <PRE>
///
/// To demonstrate that %Stream.Object does not get persisted correctly
/// with the aim of discovering a decent solution
///
/// The test results are as follows:
///
/// >Do ##class(ADK.TestingStreamObject).Ztest(1)
///
/// KILL'g Storage
/// ..%KillExtent()
/// '/tmp/tqad.txt' does exist
/// Testing character stream persistence.
/// Added '/tmp/tqad.txt' as record '1' in 'ADK.TestingStreamObject'
/// ^ADK.TestingStreamObjectD=1
/// ^ADK.TestingStreamObjectD(1)=$lb("","1")
/// ^ADK.TestingStreamObjectS=1
/// ^ADK.TestingStreamObjectS(1)=1
/// ^ADK.TestingStreamObjectS(1,0)=38
/// ^ADK.TestingStreamObjectS(1,1)="Testing character stream persistence."_$c(10)
/// +----------------- general information ---------------
/// | oref value: 2
/// | class name: ADK.TestingStreamObject
/// | %%OID: $lb("1","ADK.TestingStreamObject")
/// | reference count: 1
/// +----------------- attribute values ------------------
/// | %Concurrency = 0 <Set>
/// +----------------- swizzled references ---------------
/// | i%Document = 1 <Set>
/// | r%Document = "" <Set>
/// +-----------------------------------------------------
/// tInstance.Document contents:
/// ERROR #5002: Cache error: <INVALID OREF>zZtest+75^ADK.TestingStreamObject.1
///
///
/// >Do ##class(ADK.TestingStreamObject).Ztest(0)
///
/// ..%KillExtent()
/// '/tmp/tqad.txt' does exist
/// Testing character stream persistence.
/// Added '/tmp/tqad.txt' as record '1' in 'ADK.TestingStreamObject'
/// ^ADK.TestingStreamObjectD=1
/// ^ADK.TestingStreamObjectD(1)=$lb("","2")
/// ^ADK.TestingStreamObjectS=2
/// ^ADK.TestingStreamObjectS(1)=1
/// ^ADK.TestingStreamObjectS(1,0)=38
/// ^ADK.TestingStreamObjectS(1,1)="Testing character stream persistence."_$c(10)
/// ^ADK.TestingStreamObjectS(2)=1
/// ^ADK.TestingStreamObjectS(2,0)=38
/// ^ADK.TestingStreamObjectS(2,1)="Testing character stream persistence."_$c(10)
/// +----------------- general information ---------------
/// | oref value: 2
/// | class name: ADK.TestingStreamObject
/// | %%OID: $lb("1","ADK.TestingStreamObject")
/// | reference count: 1
/// +----------------- attribute values ------------------
/// | %Concurrency = 0 <Set>
/// +----------------- swizzled references ---------------
/// | i%Document = 2 <Set>
/// | r%Document = "" <Set>
/// +-----------------------------------------------------
/// tInstance.Document contents:
/// ERROR #5002: Cache error: <INVALID OREF>zZtest+75^ADK.TestingStreamObject.1
///
///
/// Summary of results:
/// 1) Only one record is supposed to exist even after many repetitions (due to .%KillExtent())
/// 2) Only one *D record does exist, although multiple *S records become apparent after repetition
/// 3) %Stream.Object does NOT get handled properly via the object compiler (as shown by <INVALID OREF>)
///
/// =============================================
///
/// </PRE>
///
Class ADK.TestingStreamObject Extends %Persistent
{

Property Document As %Stream.Object;

/// Store either a CHARACTER or BINARY stream under the same abstract paradigm of DOCUMENT
ClassMethod Add(pDocumentStream As %Stream.Object, Output pNewRowId As %Integer) As %Status
{
    Set pNewRowId = 0
    #Dim tException As %Exception.SystemException
    #Dim tStatus As %Status = pDocumentStream.Rewind()
    Try {
        If ($$$ISOK(tStatus)) {
            #Dim tInstance As ADK.TestingStreamObject = ..%New()
            Set tInstance.Document = $Select(pDocumentStream.%ClassName(1) [ "Binary": ##class(%Stream.GlobalBinary).%New(), 1: ##class(%Stream.GlobalCharacter).%New())
            If (tInstance.Document.Clear() + tInstance.Document.Rewind()) { }
            Set tStatus = tInstance.Document.CopyFrom(pDocumentStream)
            If ($$$ISOK(tStatus)) {
                Set tStatus = tInstance.%Save()
                If ($$$ISOK(tStatus)) && ($Increment(pNewRowId, tInstance.%Id())) { }
            }
        }
    } Catch (tException) {
        Set tStatus = tException.AsStatus()
    }
    Quit tStatus
}

ClassMethod Ztest(pKillStorage As %Boolean = 0)
{
    // 'Smoke Test' on the presumption of being a Red Hat Linux server
    #Dim tException As %Exception.SystemException
    #Dim tStatus As %Status = $$$OK
    Try {
        If (pKillStorage) {
            Write !
            Write "KILL'g Storage"
            Kill ^ADK.TestingStreamObjectD
            Kill ^ADK.TestingStreamObjectI
            Kill ^ADK.TestingStreamObjectS
            Write !
        }
        //
        Write !
        Write "..%KillExtent()"
        Write !
        Set tStatus = ..%KillExtent()
        If ($$$ISOK(tStatus)) {
        //
            #Dim testFullFileNameCharacter As %String = "/tmp/tqad.txt"
            #Dim testStreamCharacter As %Stream.FileCharacter = ##class(%Stream.FileCharacter).%New()
            Set testStreamCharacter.Filename = testFullFileNameCharacter
            If (testStreamCharacter.Clear() + testStreamCharacter.Rewind()) { }
            Set tStatus = testStreamCharacter.WriteLine("Testing character stream persistence.")
            If ($$$ISOK(tStatus)) {
                Set tStatus = testStreamCharacter.%Save()
                If ($$$ISOK(tStatus)) {
                    Write !
                    Write "'"
                    Write testFullFileNameCharacter
                    Write "' does "
                    If ('##class(%File).Exists(testFullFileNameCharacter)) {
                        Write "NOT "
                    }
                    Write "exist"
                    Write !!
                    //
                    If ($ZF(-1, "/bin/cat '" _ testFullFileNameCharacter _ "'")) { }
                    //
                    Set tStatus = testStreamCharacter.Rewind()
                    If ($$$ISOK(tStatus)) {
                        #Dim tNewRowId As %Integer = 0
                        Set tStatus = ..Add(testStreamCharacter, .tNewRowId)
                        If ($$$ISOK(tStatus)) {
                            Write !
                            Write "Added '"
                            Write testFullFileNameCharacter
                            Write "' as record '"
                            Write tNewRowId
                            Write "' in '"
                            Write ..%ClassName(1)
                            Write "'"
                            Write !!
                            //
                            ZWrite ^ADK.TestingStreamObjectD
                            Write !
                            ZWrite ^ADK.TestingStreamObjectI
                            Write !
                            ZWrite ^ADK.TestingStreamObjectS
                            Write !
                            //
                            #Dim tInstance As ADK.TestingStreamObject = ..%OpenId(tNewRowId, 0)
                            Do $System.OBJ.Dump(tInstance)
                            //
                            Write !
                            Write "tInstance.Document contents:"
                            Write !
                            Set tStatus = tInstance.Document.Rewind()
                            While ($$$ISOK(tStatus)) && ('tInstance.Document.AtEnd)
                            {
                            Write tInstance.Document.ReadLine()
                            Write !
                            }
                            //
                            Write !
                            Write "If we get to this point, everything should be OK"
                            Write !
                        }
                    }
                }
            }
        }
    }
    Catch (tException) {
        Set tStatus = tException.AsStatus()
    }
    If ($$$ISERR(tStatus)) {
    Write !
    Do $System.Status.DisplayError(tStatus)
        Write !
    }
    Quit
}
}
Discussion (3)0
Log in or sign up to continue

The reason for not storing as a file on the operating system is that this is a Cache database.

 

Keeping multiple systems up-to-date with the same content would be better served via Mirroring rather than RSYNC. The more processes required to keep the whole system up-to-date, the greater the chances that Disaster Recovery requires more work than necessary. If server migration would be required, it is easer to copy one database file than many files.

 

However, and arguably more importantly, the idea that an object is a Stream that does not obviously allow different types of inherited instances (eg. Vehicles are a super of car, van and motorbike) seems 'under-achieving'.

 

It would be possible to have two properties, one BINARY and the other CHARACTER and populate according to the passed stream type but a getter would have to be added to decide which type has been stored and fetch appropriately.

 

Having it all done seamlessly with as few extra methods as possible gives a better approach (IMO).