Striim 3.9.6 documentation

Developing multi-row functions

Multi-row functions operate across multiple rows in a Window. A best practice when designing multi-row functions is to use accumulators that eliminate the need to iterate through all the rows in a Window. As you add and eliminate rows from a Window, maintain state on the fly. While it is still possible to iterate through all the rows, the approach using accumulators and maintaining state will help improve performance.

To design a class supporting multi-row functions:

  1. Ensure that you've imported the necessary Class to declare an aggregate function.

    import com.webaction.runtime.compiler.custom.AggHandlerDesc;
  2. Declare a handler that defines a Java class used to create the TQL function signature supporting the required accumulators and functions. For example:

    @AggHandlerDesc(handler=AvgDebug_Float.class)
  3. Declare an abstract static method specifying the expected arguments and return type for the multi-row function. The Java signature will be converted to an equivalent TQL signature. For example:

        public abstract Float AvgDebug( String sKey, Float fArg );
  4. Define a public static nested class whose name is identical to the one you specified in your handler. After setting its initialization variables, your class must implement the following methods:

    • decAggValue()

    • incAggValue()

    • getAggValue()

The following example illustrates these basic steps in a class that maintains a running sum and can return the average of all floating point values through its getAggValue() function. The incAggValue() function adds a new value to the running sum, and decAggValue() removes a value from the running sum:

    // Required handler declaration:
    // handler name must match the name of your static class. 
    @AggHandlerDesc(handler=AvgDebug_Float.class)

    // Name of the function to be exported to TQL:
    public abstract Float AvgDebug( String sKey, Float fArg );

    // Handler class implementing the required accumulators:
    // must match the name in the handler declaration.
    public static class AvgDebug_Float
    {
        // initializers
        float fRunningSum = 0;
        int iRunningCount = 0;
        
        // Calculates and returns the average value at any moment
        public Float getAggValue()
        {
            logger.debug( "RunningSum: " + fRunningSum + ",
              RunningCount: " + iRunningCount + "\n");
            return (iRunningCount == 0) ? 0 : (fRunningSum / iRunningCount);
        }
        // Adds the new value to the running total, maintaining the count
        // of values included:
        public void incAggValue(String sKey, Float fArg)
        {
            if(fArg != null) {
            	iRunningCount++;
                fRunningSum += fArg;
            }
            logger.debug( "[Key: " + sKey + ", Value: " +  fArg + "]
              RunningSum is now: " + fRunningSum + ",  RunningCount is now: "
              + iRunningCount );
        }

        // Removes the specified value from the running total, maintaining
        // the count of values included:
        public void decAggValue(String sKey, Float fArg)
        {
            if(fArg != null) {
            	iRunningCount--;
                fRunningSum -= fArg;
            }
            logger.debug( "[Key: " + sKey + ", Value: " +  fArg + "]
              RunningSum is now: " + fRunningSum + ",  RunningCount is now: " 
              + iRunningCount );
        }
    }

The following TQL example illustrates the mapping between the Java code shown above and its equivalent class and function once deployed in the Striim environment:

USE StriimWindows_NS2;
DROP APPLICATION StriimWindows2 CASCADE;
 
IMPORT STATIC com.mycompany.custom.packagename.MyCustomFunctions.*;
 
CREATE APPLICATION StriimWindows2;
 
CREATE OR REPLACE CQ MovingAvg_CQ
INSERT INTO MovingAvg_ST
SELECT
     avgDebug( OrderId, OrderValue )
FROM
     MovingAvg3ES_WN;

The following Java code implements a handler for an imported TQL function called LastBut, which returns the string located one position before the last specified index:

	@AggHandlerDesc(handler=LastBut_String.class)
    public abstract String LastBut(String arg, int idx);
    public static class LastBut_String
    {
        LinkedList<String> list = new LinkedList<String>();
        int i;
        
        // Returns the string located in the position just before 
        // the last specified index:
        public String getAggValue() 
        { 
            if( list.isEmpty()  || (i < 0) || (i > (list.size()-1)) ) { 
                return null;
            } else {
                return list.get( ( list.size()-i-1) );
            }
        }
 
        // Adds a string to the end of the linked list, 
        //updating the index location:
        public void incAggValue(String arg, int idx) 
        { 
            list.addLast(arg);
            i = idx;  // no safety logic required here - 
                      // all handled by the getAggValue() method
        }


        // Removes the string located at the head of the linked list,
        // assuming it is not an empty list:
        public void decAggValue(String arg, int idx) 
        { 
            if(!list.isEmpty()) list.removeFirst();
        }
    }

Based on the Java code specified in the example above, the TQL class would be called LastBut_String, and the custom method that you could use in your TQL would be called LastBut. Based on this pattern, you can create a collection of functions that take advantage of the Java LinkedList template class, substituting other types for String, such as Byte, Short, Integer, Long, Float, Double, Boolean, DateTime, and even Object. For example, the following example extends the pattern to a linked list of Byte objects:

    @AggHandlerDesc(handler=LastBut_Byte.class)
    public abstract Byte LastBut(Byte arg, int idx);
    public static class LastBut_Byte
    {
        LinkedList<Byte> list = new LinkedList<Byte>();
        int i;
        
        public Byte getAggValue() 
        { 
            if( list.isEmpty()  || (i < 0) || (i > (list.size()-1)) ) { 
                return null;
            } else {
                return list.get( ( list.size()-i-1) );
            }
        }
        public void incAggValue(Byte arg, int idx) 
        { 
            list.addLast(arg);
            i = idx;  // no safety logic required here - 
                      // all handled by the getAggValue() method
        }
        public void decAggValue(Byte arg, int idx) 
        { 
            if(!list.isEmpty()) list.removeFirst();
        }
    }