About Streams
A stream is an abstract representation of a stream of characters (or bytes) that can be manipulated using a common interface. Caché contains a number of stream classes that provide access to different data sources using the common stream interface.
A special class of streams, derived from %SerialStream, are used to create properties containing large (greater than 32K) amounts of data. From the object model's point of view, a stream looks like a special type of collection and is defined in a similar fashion, using the stream and bstream attribute modifiers:
// person.cdl

class Person {
	attribute Name { type = %String; }
	stream attribute Memo { type = %Stream; }
	bstream attribute Picture { type = %Stream; }
}
There are two styles of stream attributes: Character streams and Binary Streams (stream and bstream). The only difference is that Character streams may undergo a UNICODE translation within client applications (similar to the difference between attributes of type %String and %Binary). Typically stream is used for text fields and bstream is used for pictures.
A stream attribute must be defined using the %Stream datatype. The %Stream datatype introduces two datatype parameters, STORAGE and LOCATION, that can be used to provide storage control over stream attributes.
The STORAGE parameter determines how the stream attribute is stored and can be set to "GLOBAL" or "FILE". If it is not specified, the default is "GLOBAL". If STORAGE is set to "GLOBAL" then the stream data is stored within the Caché database as a set of global nodes. If STORAGE is set to "FILE" then the stream is stored in an external file.
The LOCATION parameter contains a string specifying where the stream data is stored. In the case of a GLOBAL stream, this is a variable name with (optional) leading subscripts. In the case of a FILE stream, this is a valid, pre-existing directory name.
For example:
	stream attribute GMemo {type = %Stream(STORAGE="GLOBAL", LOCATION="^MyStream";}
	stream attribute FMemo {type = %Stream(STORAGE="FILE", LOCATION="C:\Stream";}
In this case, the attribute GMemo will be a stream stored in the ^MyStream global while FMemo will be stored in a file (with an automatically generated name) in the "C:\Stream" directory.
The default location for a GLOBAL stream is ^ooClassNameS. The default location for a FILE stream is the current Caché directory.

Streams have a temporary and a permanent storage location so that you can start inserting into a stream, then decide that you want to abandon the insert and the stream will still have the permanent information stored. All inserts go into the temporary storage area and this is only made permanent when you save the stream.

If you create a stream, start inserting, then do some reading you can call MoveToEnd() and then continue appending to the stream and this will work fine. However after you save the stream the data is moved to the permanent storage location so if you reload the stream and start inserting it will insert into the temporary storage area and so you will not be appending to the permanently stored data.

If this is the behavior you want you will need to create a temporary stream, for example:

  s t=##class(Test).%OpenId(5)
  s tmpstream=##class(%GlobalCharacterStream).%New()
  d tmpstream.CopyFrom(t.text)
  d tmpstream.MoveToEnd()
  d tmpstream.Write("append text")
  s t.text=tmpstream
  d tmpstream.%Close()
  <Now do whatever you want with 't' object>

As you can see I create a temporary stream of the type I require, copy from the stream stored in my object which will put this data in the temporary storage area of my new stream. Then I append to this stream and put its oref into the 't' object and close it to keep the reference counts correct.
The Stream Interface
All streams share a common, simple interface. This interface is defined by the %AbstractStream class. At the core of this interface are the methods Read, Write, and Rewind and the properties AtEnd and Size. For example, to read the data from a stream attribute:
Do person.Memo.Rewind()

; write contents of stream to console, 100 characters at a time
; note that we test AtEnd to find when we are the end of the stream
; len is passed by reference and may be changed by Read, so we set it back to 100
; for each call

For  Set len=100 Quit:person.Memo.AtEnd Write person.Memo.Read(.len)
Similarly, you can write data into the stream:
Do person.Memo.Write("This is some text. ")
Do person.Memo.Write("This is some more text.")
All streams contain a CopyFrom method which allows one stream to fill itself from another stream. This can be used, for example, to copy data from a file into a stream attribute:
; open a text file using a %File stream
Set file=##class(%File).%New("\data\textfile.txt")
Do file.Open("RU") ; same flags as OPEN command--use "U" for streams

; now open a Person object containing a Memo stream
; it does not matter what kind of stream Memo is...

Set person=##class(Person).%New()

; copy the file into Memo
Do person.Memo.CopyFrom(file)

; save the person object and close it
Do person.%Save()
Do person.%Close()

; close the file
Do file.%Close()
Using Streams in Object Applications
Within an object application, stream attributes are used just as collections; they are manipulated via a transient object that is created by the object that owns the stream attribute. For example:
; create object and stream
Set p=##class(Person).%New()
Set p.Name="Mo"
Do p.Memo.Write("This is part one of a long memo")
; ...

Do p.Memo.Write("This is part 10000 of a long memo")

Do p.%Save()

Do p.%Close()

; read object and stream

Set p=##class(Person).%Open(oid)

Do p.Memo.Rewind() ; not required first time

; write contents of stream to console, 100 characters at a time

For  Set len=100 Quit:p.Memo.AtEnd Write p.Memo.Read(.len)

Do p.%Close()
Note that streams act as literal values (think of them as a large string). Two object instances cannot refer to the same stream.
The Stream Class Hierarchy
Streams are implemented via a family of classes:
%AbstractStream Abstract stream class; all streams inherit from this.
%SerialStream Abstract class that is the super class of all streams that can be used as object attributes.
%GlobalCharacterStream Concrete class for character stream stored in global nodes.
%GlobalBinaryStream Concrete class for binary stream stored in global nodes.
%FileCharacterStream Concrete class for character stream stored in an external file.
%FileBinaryStream Concrete class for binary stream stored in an external file.
%File A class for manipulating external files as streams.
Using Streams with SQL
Stream fields have the following restrictions within SQL:
Within embedded SQL you can read a stream as follows: First, select the stream:
&sql(SELECT Memo INTO :memo FROM Person WHERE ID = 1)
This will fetch the "Stream Id" value used to store the stream into memo (not the value of the stream). Now you can open a %SerialStream object from this OID and use the Stream API:
Set stream=##class(%SerialStream).%Open(memo)
Note that the %Open method is fully polymorphic; the actual type of stream returned by %Open depends on what type is stored with the Person object.
Now we can read the stream:
Write stream.Read(100)
And close it when we are done:
Do stream.%Close()
Writing a stream is similar. First, create a serial stream and write data to it:
Set stream=##class(%GlobalCharacterStream).%New()
Do stream.Write("Do re mi fa sol...")

; Now save the stream:
Do stream.SaveStream()

; Now get the OID value for the Stream:
Set memo=stream.GetStreamId()

; Now we INSERT this oid value into the table:
&sql(INSERT INTO Person (Name,Memo) VALUES (:name, :memo))

; Close the stream when we are done
Do stream.%Close()
Streams and ODBC
Streams are projected to ODBC as BLOBs: their ODBC type is LONG VARCHAR (or LONG VARBINARY). The ODBC driver/server uses a special protocol to read/write BLOBs. Typically you have to write BLOB applications by hand; the standard reporting tools do not support them.
Streams and Visual Basic
To make it easy to use streams efficiently within Visual Basic applications the Caché ActiveX Binding projects stream attributes using two special ActiveX stream objects: CacheObject.CharStream and CacheObject.BinaryStream. The operation of these objects is automatic; from your Visual Basic application you simply make calls to the methods the objects provide.
For example, suppose you have a Person object containing a character stream property, Memo. In addition to the the standard stream methods Read and Write, you can use the client-side method Data which will fetch the entire stream in one operation:
' Visual Basic code
Dim person As Object
Dim memo As String

' Open a Person object and copy its memo into a local variable
Set person=Factory.OpenId(id)
memo=person.Memo.Data
For binary streams there is a similar mechanism to for efficiently copying binary data containing a bitmap image into a picture control. For example, if the Person object has a binary stream attribute, Picture, containing a bitmap image (such as a .jpg or .gif file), you can display this image in Visual Basic as follows:
' Visual Basic code
Dim person As Object
Dim memo As String

' Open a Person object and show its picture in an Image control
Set person=Factory.OpenId(id)
Image1.Picture = person.Picture.GetPicture
Similarly, you can copy an image from an Image control to a binary stream property using the SetPicture method.
' Visual Basic code
person.Picture.SetPicture Image1.Picture