package jeql.engine.function; import java.util.List; import jeql.engine.CompilationException; import jeql.engine.Scope; import jeql.engine.query.QueryScope; import jeql.syntax.ParseTreeNode; import jeql.util.TypeUtil; /** * Implements the INDEX pseudo-function semantics. * <p> * Syntax: * <pre> * INDEX( inc-val [, reset-val ] ) * </pre> * INDEX returns a integer value * which is incremented whenever the value of inc-val changes. * The counter value is reset whenever the value of reset-val changes. * The initial value of INDEX(...) is 1. * * This allows assigning sequence numbers to streams of * changing values where the values are not known in advance. * * @author Martin Davis * */ public class IndexFunctionEvaluator implements FunctionEvaluator { private int argCount = 0; private ParseTreeNode incCondExpr; private ParseTreeNode resetExpr = null; public static final String FN_NAME = "index"; public IndexFunctionEvaluator() { } public void bind(Scope scope, List args) { argCount = args.size(); incCondExpr = (ParseTreeNode) args.get(0); if (args.size() >= 2) resetExpr = (ParseTreeNode) args.get(1); if (argCount < 1 || argCount > 2) throw new CompilationException("INDEX() function must have 1 or 2 arguments"); } public Object eval(Scope scope) { // if this fails, function is being called outside of a SELECT => error QueryScope qScope = (QueryScope) scope; IndexValueTracker track = (IndexValueTracker) qScope.getValue(this); if (track == null || qScope.getRowNum() <= 1) { // TODO: add initial value optional parameter track = new IndexValueTracker(); qScope.setValue(this, track); } Object incCondObj = incCondExpr.eval(scope); Object resetObj = null; if (resetExpr != null) resetObj = resetExpr.eval(scope); track.update(incCondObj, resetObj); return track.getIndex(); } public Class getType(Scope scope) { return Integer.class; } class IndexValueTracker { private boolean isFirst = true; private int initialValue = 1; private int index = 1; private Object currIncVal = null; private Object currResetVal = null; public IndexValueTracker() { // TODO: allow initial value to be set by user index = initialValue; } public int getIndex() { return index; } public void update(Object incVal, Object resetVal) { if (isFirst) { currIncVal = incVal; currResetVal = resetVal; isFirst = false; index = initialValue; return; } if (isFirst || ! eq(resetVal, currResetVal)) { index = initialValue; currResetVal = resetVal; if (! eq(incVal, currIncVal)) { index++; } currIncVal = incVal; } else if (! eq(incVal, currIncVal)) { index++; currIncVal = incVal; } isFirst = false; } public boolean eq(Object o1, Object o2) { if (o1 == null) { return o2 == null; } return o1.equals(o2); } } }