To write an aggregate function, you must have a place to store your data between invocations. For example, if you write your own "SUM()" function, you must store the subtotal from the previous calls to the function and then add the new value from the current call to the function.
Since an aggregate function may be called by many queries at overlapping times, you cannot use local storage space to store values such as subtotals. If you store the subtotal in a static variable declared in the routine, then each different call from different queries would update the same variable, and the result would be meaningless. A similar problem occurs if you try to use global variables. Non-static local variables (in other words, stack-based variables) can't be used because the value may be overwritten between calls to the function.
Your aggregate function must work with the server to store data in a place that will persist after the UDF returns from its call. Sybase CEP provides a pair of functions that enable you to store and retrieve data. These functions are documented in the relevant SDK chapters.
The bytes stored and retrieved are specific to a particular occurrence of a particular function in a particular CCL statement. If your query module has the following statements:
... SELECT aggregate_foo(col1) FROM InStream1 ... SELECT aggregate_foo(col2) FROM InStream2
then each "aggregate_foo" will have a unique internal identifier that the server uses as part of the context variable passed to your function. The context variable then allows the server to know which aggregate_foo's bytes to return. This is explained in more detail in the relevant SDK chapters.
There is a second hurdle that an aggregate function must overcome. An aggregate function operates on a window (for example, the window created by a "KEEP 3 ROWS" or "KEEP 10 MINUTES" clause). Such a window has rows entering and leaving. If the window is defined by a KEEP 3 ROWS clause, for example, then each time that a new row is received, the new row will displace the oldest row. However, if you are writing a SUM() function, and you only want the sum of the three most recent records, then each time that a new record arrives, you must not only increase the sum based on the value from the most recent record, but you must also decrease the sum based on the value from the record that was displaced from the window.
To ensure that your aggregate UDF takes into account the displaced record as well as the new record, your aggregate UDF will usually be called twice each time that a new record arrives. Your aggregate UDF will be called once with the value of the new record and once with the value of the displaced record. Inside your aggregate UDF, you will distinguish between the new and displaced values by calling a function named C8IsPositiveMessage() or C8IsNegativeMessage(). When the function is called with the newly-added value, then C8PositiveMessage() will return C8_TRUE, and C8NegativeMessage() will return C8_FALSE. When the function was called with the displaced value, then C8PositiveMessage() will return C8_FALSE and C8NegativeMessage() will return C8_TRUE.