changeset 1276:5d3611202f8a

Rename StatementDescriptorParser => BasicDescriptorParser. Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-October/008412.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Fri, 04 Oct 2013 18:13:31 +0200
parents 8226136dd44d
children 6f704bf1d5ac
files storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BasicDescriptorParser.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/BasicDescriptorParserTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/WhereExpressions.java
diffstat 8 files changed, 2901 insertions(+), 2901 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BasicDescriptorParser.java	Fri Oct 04 18:13:31 2013 +0200
@@ -0,0 +1,887 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.internal.statement;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction;
+import com.redhat.thermostat.storage.core.BackingStorage;
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.ParsedStatement;
+import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.Replace;
+import com.redhat.thermostat.storage.core.Statement;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.Update;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
+import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
+
+/**
+ * A parser for the string representation of {@link StatementDescriptor}s.
+ * Tokens have to be separated by whitespace.
+ * 
+ * This parser implements the following simple grammar for statement descriptors.
+ * It supports the following statement types:
+ * <ul>
+ * <li>QUERY (read)</li>
+ * <li>QUERY-COUNT (read)</li>
+ * <li>ADD (write)</li>
+ * <li>UPDATE (write)</li>
+ * <li>REPLACE (write)</li>
+ * <li>REMOVE (write)</li>
+ * </ul>
+ * 
+ * <p><strong>Grammar:</strong></p>
+ * <pre>
+ * statementDesc := statementType category setList suffix
+ * statementType := 'QUERY' | 'QUERY-COUNT' |
+ *                  'ADD' | 'REPLACE' | 'UPDATE' |
+ *                  'REMOVE'
+ * category      := string
+ * setList       := 'SET' setValues | \empty
+ * setValues     := valuePair valueList
+ * valuePair     := term '=' term
+ * valueList     := ',' setValues | \empty
+ * suffix        := 'WHERE' where |
+ *                  'SORT' sortCond |
+ *                  'LIMIT' term | \empty
+ * where         := whereExp sort limit
+ * whereExp      := andCond orCond
+ * orCond        := 'OR' whereExp | \empty
+ * sort          := 'SORT' sortCond | \empty
+ * sortCond      := sortPair sortList
+ * sortPair      := term sortModifier
+ * sortModifier  := 'ASC' | 'DSC'
+ * sortList      := ',' sortCond | \empty
+ * limit         := 'LIMIT' term | \empty
+ * andCond       := condition andBody
+ * andBody       := 'AND' whereExp | \empty
+ * condition     := 'NOT' condition | compExp
+ * compExp       := term compExpRHS
+ * term          := freeParam | literal
+ * freeParam     := '?s' | '?i' | '?l' | '?s[' | '?b'
+ * literal       := sQuote string sQuote | int | long | boolean
+ * sQuote        := \'
+ * boolean       := &lt;true&gt; | &lt;false&gt;
+ * int           := &lt;literal-int&gt;
+ * long          := &lt;literal-long&gt;longPostFix
+ * longPostFix   := 'l' | 'L'
+ * string        := &lt;literal-string-value&gt;
+ * compExpRHS    := '!=' term | '=' term | '&lt;=' term | '&gt;=' term | 
+ *                  '&lt;' term | '&gt;' term
+ * </pre>
+ *
+ * This implements the following logic precedence rules (in this order of
+ * precedence):
+ * 
+ * <ol>
+ *   <li>NOT</li>
+ *   <li>AND</li>
+ *   <li>OR</li>
+ * </ol>
+ * 
+ * NOTE: Comparison expressions have equal precedence.
+ */
+class BasicDescriptorParser<T extends Pojo> {
+
+    private static final String TOKEN_DELIMS = " \t\r\n\f";
+    private static final short IDX_QUERY = 0;
+    private static final short IDX_QUERY_COUNT = 1;
+    private static final short IDX_ADD = 2;
+    private static final short IDX_REPLACE = 3;
+    private static final short IDX_UPDATE = 4;
+    private static final short IDX_REMOVE = 5;
+    private static final String[] KNOWN_STATEMENT_TYPES = new String[] {
+        "QUERY", "QUERY-COUNT", "ADD", "REPLACE", "UPDATE", "REMOVE"
+    };
+    private static final String SORTLIST_SEP = ",";
+    private static final String SETLIST_SEP = SORTLIST_SEP;
+    private static final String KEYWORD_SET = "SET";
+    private static final String KEYWORD_WHERE = "WHERE";
+    private static final String KEYWORD_SORT = "SORT";
+    private static final String KEYWORD_LIMIT = "LIMIT";
+    private static final String KEYWORD_ASC = "ASC";
+    private static final String KEYWORD_DSC = "DSC";
+    private static final String POJO_FREE_PARAMETER_TYPE = "?p";
+    private static final char PARAM_PLACEHOLDER = '?';
+    
+    private final String[] tokens;
+    private final StatementDescriptor<T> desc;
+    private final BackingStorage storage;
+    private int currTokenIndex;
+    private int placeHolderCount;
+    // the parsed statement
+    private ParsedStatementImpl<T> parsedStatement;
+    private SuffixExpression tree;
+    private SetList setList;
+    
+    BasicDescriptorParser(BackingStorage storage, StatementDescriptor<T> desc) {
+        this.tokens = getTokens(desc.getDescriptor());
+        this.currTokenIndex = 0;
+        this.placeHolderCount = 0;
+        this.desc = desc;
+        this.storage = storage;
+    }
+    
+    private String[] getTokens(String str) {
+        StringTokenizer tokenizer = new StringTokenizer(str, TOKEN_DELIMS);
+        List<String> toks = new ArrayList<>(tokenizer.countTokens());
+        while (tokenizer.hasMoreTokens()) {
+            toks.add(tokenizer.nextToken());
+        }
+        return toks.toArray(new String[0]);
+    }
+
+    public ParsedStatement<T> parse() throws DescriptorParsingException {
+        matchStatementType();
+        matchCategory();
+        // matched so far, create the raw statement
+        createStatement();
+        this.setList = new SetList();
+        matchSetList(setList);
+        this.tree = new SuffixExpression();
+        matchSuffix();
+        if (currTokenIndex != tokens.length) {
+            throw new DescriptorParsingException("Incomplete parse");
+        }
+        parsedStatement.setNumFreeParams(placeHolderCount);
+        parsedStatement.setSetList(setList);
+        parsedStatement.setSuffixExpression(tree);
+        doSemanticAnalysis();
+        return parsedStatement;
+    }
+
+    private void doSemanticAnalysis() throws DescriptorParsingException {
+        // TODO:
+        // - Check that ADD/REPLACE specifies all keys judging by the Pojo
+        //   model class. Not sure if good idea though, as this would likely
+        //   introduce dep on beanutils.
+        Statement<T> stmt = parsedStatement.getRawStatement();
+        if (stmt == null) {
+            // should never be null
+            throw new NullPointerException();
+        }
+        if (stmt instanceof Add && tree.getWhereExpn() != null) {
+            String msg = "WHERE clause not allowed for ADD";
+            throw new DescriptorParsingException(msg);
+        }
+        if (stmt instanceof Replace && tree.getWhereExpn() == null) {
+            String msg = "WHERE clause required for REPLACE";
+            throw new DescriptorParsingException(msg);
+        }
+        if (stmt instanceof Update) {
+            if (tree.getWhereExpn() == null) {
+                // WHERE required for UPDATE
+                String msg = "WHERE clause required for UPDATE";
+                throw new DescriptorParsingException(msg);
+            }
+            if (setList.getValues().size() == 0) {
+                // SET required for UPDATE
+                String msg = "SET list required for UPDATE";
+                throw new DescriptorParsingException(msg);
+            }
+        }
+        if (stmt instanceof Remove && setList.getValues().size() > 0) {
+            String msg = "SET not allowed for REMOVE";
+            throw new DescriptorParsingException(msg);
+        }
+        if (stmt instanceof Query) {
+            if (setList.getValues().size() > 0) {
+                // Must not have SET for QUERYs
+                String msg = "SET not allowed for QUERY/QUERY-COUNT";
+                throw new DescriptorParsingException(msg);
+            }
+        } else {
+            // only queries can have sort/limit expressions
+            if (this.tree.getLimitExpn() != null || this.tree.getSortExpn() != null) {
+                String msg = "LIMIT/SORT only allowed for QUERY/QUERY-COUNT";
+                throw new DescriptorParsingException(msg);
+            }
+        }
+    }
+
+    /*
+     * Match set list for DML statements.
+     */
+    private void matchSetList(final SetList setList) throws DescriptorParsingException {
+        if (tokens.length == currTokenIndex) {
+            // no set list
+            return;
+        }
+        if (tokens[currTokenIndex].equals(KEYWORD_SET)) {
+            currTokenIndex++; // SET
+            matchSetValues(setList);
+        }
+        // empty, proceed with suffix
+    }
+
+    /*
+     * Match list of values in a SET expression 
+     */
+    private void matchSetValues(SetList setList) throws DescriptorParsingException {
+        matchValuePair(setList);
+        matchValueList(setList);
+    }
+
+    /*
+     * Match more value pairs in a SET list
+     */
+    private void matchValueList(SetList setList) throws DescriptorParsingException {
+        if (currTokenIndex == tokens.length) {
+            // empty
+            return;
+        }
+        if (tokens[currTokenIndex].equals(SETLIST_SEP)) {
+            currTokenIndex++; // ,
+            matchSetValues(setList);
+        }
+    }
+
+    /*
+     * Match one pair of values in a 
+     */
+    private void matchValuePair(SetList setList) throws DescriptorParsingException {
+        SetListValue value = new SetListValue();
+        TerminalNode lval = new TerminalNode(null);
+        matchTerm(lval, true, true);
+        value.setKey(lval);
+        if (tokens[currTokenIndex].equals("=")) {
+            currTokenIndex++; // =
+        } else {
+            String msg = "Expected '=' after SET value LHS. Token was ->" + tokens[currTokenIndex] + "<-";
+            throw new DescriptorParsingException(msg);
+        }
+        TerminalNode rval = new TerminalNode(null);
+        matchTerm(rval, false, true);
+        value.setValue(rval);
+        setList.addValue(value);
+    }
+
+    /*
+     * Match optional suffixes. 
+     */
+    private void matchSuffix() throws DescriptorParsingException {
+        if (tokens.length == currTokenIndex) {
+            // no suffix
+            return;
+        }
+        if (tokens[currTokenIndex].equals(KEYWORD_WHERE)) {
+            currTokenIndex++;
+            WhereExpression expn = new WhereExpression();
+            tree.setWhereExpn(expn);
+            matchWhereExp(expn.getRoot());
+            matchSort(tree);
+            matchLimit(tree);
+        } else if (tokens[currTokenIndex].equals(KEYWORD_SORT)) {
+            // SORT token eaten up by matchSort()
+            matchSort(tree);
+            matchLimit(tree);
+        } else if (tokens[currTokenIndex].equals(KEYWORD_LIMIT)) {
+            // LIMIT token eaten up by matchLimit()
+            matchLimit(tree);
+        } else {
+            throw new DescriptorParsingException("Unexpected token: '"
+                    + tokens[currTokenIndex] + "'. Expected one of "
+                    + KEYWORD_WHERE + ", " + KEYWORD_SORT + ", " + KEYWORD_LIMIT);
+        }
+    }
+
+    private void matchLimit(SuffixExpression tree) throws DescriptorParsingException {
+        if (currTokenIndex == tokens.length) {
+            // empty
+            return;
+        } else if (currTokenIndex < tokens.length) {
+            if (tokens[currTokenIndex].equals(KEYWORD_LIMIT)) {
+                LimitExpression node = new LimitExpression();
+                tree.setLimitExpn(node);
+                currTokenIndex++;
+                matchTerm(node);
+            }
+        } else {
+            throw new DescriptorParsingException("Illegal statement descriptor: Reason LIMIT");
+        }
+    }
+
+    private void matchSort(SuffixExpression tree) throws DescriptorParsingException {
+        if (currTokenIndex < tokens.length
+                && tokens[currTokenIndex].equals(KEYWORD_SORT)) {
+            SortExpression sortExpn = new SortExpression();
+            tree.setSortExpn(sortExpn);
+            currTokenIndex++;
+            matchSortList(sortExpn);
+        } 
+        if (currTokenIndex > tokens.length) {
+            throw new DescriptorParsingException("Illegal statement descriptor.");
+        }
+        // empty
+    }
+
+    private void matchSortList(SortExpression sortExpn) throws DescriptorParsingException {
+        matchSortPair(sortExpn);
+        matchSortListPreamble(sortExpn);
+    }
+
+    private void matchSortListPreamble(SortExpression sortExpn) throws DescriptorParsingException {
+        if (currTokenIndex < tokens.length && tokens[currTokenIndex].equals(SORTLIST_SEP)) {
+            currTokenIndex++; // ',' token
+            matchSortList(sortExpn);
+        }
+    }
+
+    private void matchSortPair(SortExpression expn) throws DescriptorParsingException {
+        SortMember member = new SortMember();
+        matchTerm(member);
+        matchSortModifier(member);
+        // Add the member node to the list of the sort node
+        expn.addMember(member);
+    }
+
+    private void matchSortModifier(SortMember member) throws DescriptorParsingException {
+        String msg = "Illegal statement decriptor: Reason SORT. Expected ASC or DSC";
+        if (currTokenIndex >= tokens.length) {
+            throw new DescriptorParsingException(msg);
+        }
+        if (tokens[currTokenIndex].equals(KEYWORD_ASC)) {
+            member.setDirection(SortDirection.ASCENDING);
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KEYWORD_DSC)) {
+            member.setDirection(SortDirection.DESCENDING);
+            currTokenIndex++;
+        } else {
+            throw new DescriptorParsingException(msg);
+        }
+    }
+
+    private void matchWhereExp(Node node) throws DescriptorParsingException {
+        if (currTokenIndex >= tokens.length) {
+            throw new DescriptorParsingException("Illegal where clause");
+        }
+        assert(node != null);
+        matchAndCondition(node);
+        matchOrCondition(node);
+    }
+
+    private void matchAndCondition(Node currNode) throws DescriptorParsingException {
+        matchCondition(currNode);
+        matchAndExpression(currNode);
+    }
+
+    private void matchCondition(Node currNode) throws DescriptorParsingException {
+        if (currTokenIndex >= tokens.length) {
+            throw new DescriptorParsingException("Illegal statement descriptor: Reason sort clause");
+        }
+        if (tokens[currTokenIndex].equals(Operator.NOT.getName())) {
+            NotBooleanExpressionNode notNode = new NotBooleanExpressionNode(currNode);
+            if (currNode instanceof BinaryExpressionNode) {
+                BinaryExpressionNode currNodeExpr = (BinaryExpressionNode)currNode;
+                Node available = currNodeExpr.getLeftChild();
+                if (available != null) {
+                    currNodeExpr.setRightChild(notNode);
+                } else {
+                    currNodeExpr.setLeftChild(notNode);
+                }
+            } else {
+                assert(currNode instanceof NotBooleanExpressionNode || currNode instanceof Node);
+                currNode.setValue(notNode);
+            }
+            currTokenIndex++; // NOT keyword
+            
+            matchCondition(notNode);
+        } else {
+            matchComparisionExpression(currNode);
+        }
+    }
+
+    private void matchComparisionExpression(Node currNode) throws DescriptorParsingException {
+        if (currTokenIndex >= tokens.length) {
+            throw new DescriptorParsingException("Illegal statement descriptor: Comparison expression");
+        }
+        BinaryExpressionNode expr = new BinaryExpressionNode(currNode);
+        TerminalNode left = new TerminalNode(expr);
+        TerminalNode right = new TerminalNode(expr);
+        expr.setLeftChild(left);
+        expr.setRightChild(right);
+        
+        if (currNode instanceof BinaryExpressionNode) {
+            BinaryExpressionNode currNodeExpr = (BinaryExpressionNode)currNode;
+            Node available = currNodeExpr.getLeftChild();
+            if (available == null) {
+                currNodeExpr.setLeftChild(expr);
+            } else {
+                assert(currNodeExpr.getRightChild() == null);
+                currNodeExpr.setRightChild(expr);
+            }
+        } else {
+            assert(currNode instanceof NotBooleanExpressionNode || currNode instanceof Node);
+            currNode.setValue(expr);
+        }
+        
+        matchTerm(left, true);
+        matchComparisonRHS(expr);
+    }
+
+    private void matchComparisonRHS(BinaryExpressionNode currNode) throws DescriptorParsingException {
+        if (currTokenIndex >= tokens.length) {
+            // boolean literals are not allowed
+            throw new DescriptorParsingException("Illegal statement descriptor: Boolean literals are not allowed!");
+        }
+        if (tokens[currTokenIndex].equals(Operator.EQUALS.getName())) {
+            currTokenIndex++;
+            currNode.setOperator(BinaryComparisonOperator.EQUALS);
+            matchTerm((TerminalNode)currNode.getRightChild(), false);
+        } else if (tokens[currTokenIndex].equals(Operator.LESS_THAN_OR_EQUAL_TO.getName())) {
+            currTokenIndex++;
+            currNode.setOperator(BinaryComparisonOperator.LESS_THAN_OR_EQUAL_TO);
+            matchTerm((TerminalNode)currNode.getRightChild(), false);
+        } else if (tokens[currTokenIndex].equals(Operator.GREATER_THAN_OR_EQUAL_TO.getName())) {
+            currTokenIndex++;
+            currNode.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
+            matchTerm((TerminalNode)currNode.getRightChild(), false);
+        } else if (tokens[currTokenIndex].equals(Operator.GREATER_THAN.getName())) {
+            currTokenIndex++;
+            currNode.setOperator(BinaryComparisonOperator.GREATER_THAN);
+            matchTerm((TerminalNode)currNode.getRightChild(), false);
+        } else if (tokens[currTokenIndex].equals(Operator.LESS_THAN.getName())) {
+            currTokenIndex++;
+            currNode.setOperator(BinaryComparisonOperator.LESS_THAN);
+            matchTerm((TerminalNode)currNode.getRightChild(), false);
+        } else if (tokens[currTokenIndex].equals(Operator.NOT_EQUAL_TO.getName())) {
+            currTokenIndex++;
+            currNode.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
+            matchTerm((TerminalNode)currNode.getRightChild(), false);
+        } else {
+            throw new DescriptorParsingException("Illegal statement descriptor: Reason comparison expression!");
+        }
+    }
+    
+    private void matchTerm(SortMember member) throws DescriptorParsingException {
+        String term = getTerm();
+        if (term.charAt(0) == PARAM_PLACEHOLDER) {
+            assert(placeHolderCount > 0);
+            ensureValidType(term, "SORT");
+            if (term.charAt(1) != 's') {
+                String msg = "Sort parameters only accept string types. Placeholder was: " + term;
+                throw new DescriptorParsingException(msg);
+            }
+            UnfinishedSortKey unfinishedKey = new UnfinishedSortKey();
+            unfinishedKey.setParameterIndex(placeHolderCount - 1);
+            member.setSortKey(unfinishedKey);
+            return;
+        }
+        String stringTerm = getStringTerm(term);
+        member.setSortKey(stringTerm);
+    }
+    
+    /*
+     * Check if the free parameter type is valid in a given context. Currently,
+     * list and pojo free type parameters are invalid for LIMIT, SORT and WHERE.
+     *  
+     * Currently, no list types and no Pojo types are allowed.
+     */
+    private void ensureValidType(String term, String contextName) throws DescriptorParsingException {
+        if (term.length() > 2) {
+            // Don't allow list types for invalid contexts
+            // Only list type free variables have 3 characters
+            String format = "List free variable type not allowed in %s context";
+            String msg = String.format(format, contextName);
+            throw new DescriptorParsingException(msg);
+        }
+        if (term.equals(POJO_FREE_PARAMETER_TYPE)) {
+            String format = "Pojo free variable type not allowed in %s context";
+            String msg = String.format(format, contextName);
+            throw new DescriptorParsingException(msg);
+        }
+    }
+    
+    private void matchTerm(LimitExpression expn) throws DescriptorParsingException {
+        String term = getTerm();
+        if (term.charAt(0) == PARAM_PLACEHOLDER) {
+            assert(placeHolderCount > 0);
+            ensureValidType(term, "LIMIT");
+            if (term.charAt(1) != 'i') {
+                String msg = "Limit parameters only accept integer types. Placeholder was: " + term;
+                throw new DescriptorParsingException(msg);
+            }
+            UnfinishedLimitValue limitValue = new UnfinishedLimitValue();
+            limitValue.setParameterIndex(placeHolderCount - 1);
+            expn.setValue(limitValue);
+            return;
+        }
+        int limitVal;
+        try {
+            limitVal = Integer.parseInt(term);
+        } catch (NumberFormatException e) {
+            throw new DescriptorParsingException("Invalid limit expression. '" + term + "' not an integer");
+        }
+        expn.setValue(limitVal);
+    }
+    
+    /**
+     * Calls {@link #matchTerm(TerminalNode, boolean, boolean)} with a
+     * {@code false isSetListContext} parameter.
+     */
+    private void matchTerm(TerminalNode node, boolean isLHS) throws DescriptorParsingException {
+        // default to false
+        matchTerm(node, isLHS, false);
+    }
+
+    /**
+     * Match a terminal in a TerminalNode context. Only where expression, and
+     * set list context take this code path.
+     * 
+     * @param node
+     *            The terminal node which is parsed at this point.
+     * @param isLHS
+     *            {@code true} if and only if the node is the left hand side of
+     *            a binary expression.
+     * @param isSetListContext
+     *            {@code true} if and only if the node is in a set list context
+     *            or conversely NOT in a where expression context.
+     * @throws DescriptorParsingException
+     *             If and error was encountered parsing the terminal string
+     *             token.
+     */
+    private void matchTerm(TerminalNode node, boolean isLHS,
+            boolean isSetListContext) throws DescriptorParsingException {
+        String term = getTerm();
+        if (term.charAt(0) == PARAM_PLACEHOLDER) {
+            assert(placeHolderCount > 0);
+            if (!isSetListContext) {
+                ensureValidType(term, "WHERE");
+            }
+            UnfinishedValueNode patchNode = new UnfinishedValueNode();
+            patchNode.setParameterIndex(placeHolderCount - 1);
+            patchNode.setLHS(isLHS);
+            // figure out the expected type
+            FreeVarTypeToken expectedType = getType(term.substring(1));
+            if (expectedType == null) {
+                throw new DescriptorParsingException("Unknown type of free parameter: '" + term + "'");
+            }
+            patchNode.setType(expectedType.componentType);
+            patchNode.setArrayType(expectedType.isArrayType);
+            node.setValue(patchNode);
+            return;
+        }
+        // regular terminal. i.e. literal value
+        if (isLHS) {
+            // FIXME: In thermostat LHS of comparisons must be Key objects. I'm
+            // not sure if this restriction is very meaningful in a prepared
+            // statement context as the purpose of this was to ensure "type"
+            // compatibility between Key <=> value comparisons.
+            String stringTerm = getStringTerm(term);
+            Key<?> key = new Key<>(stringTerm);
+            node.setValue(key);
+        } else {
+            Object typedValue = getTypedValue(term);
+            node.setValue(typedValue);
+        }
+    }
+    
+    private Object getTypedValue(String term) throws DescriptorParsingException {
+        try {
+            String stringTerm = getStringTerm(term);
+            return stringTerm;
+        } catch (DescriptorParsingException e) {
+            // Must be integer (long/int) or boolean. First check for boolean,
+            // then for long and regular ints.
+            if (term.equals(Boolean.toString(false)) || term.equals(Boolean.toString(true))) {
+                boolean boolVal = Boolean.parseBoolean(term);
+                return boolVal;
+            }
+            // Next, parse long or int.
+            try {
+                int lastCharInTokenIndex = term.length() - 1;
+                // preceding l/L indicate long integer types.
+                if (term.charAt(lastCharInTokenIndex) == 'L' || term.charAt(lastCharInTokenIndex) == 'l') {
+                    long longVal = Long.parseLong(term.substring(0, lastCharInTokenIndex));
+                    return longVal;
+                }
+                // must be integer or some invalid type
+                int intVal = Integer.parseInt(term);
+                return intVal;
+            } catch (NumberFormatException nfe) {
+                String msg = "Illegal terminal type. Token was ->" + term + "<-";
+                throw new DescriptorParsingException(msg);
+            }
+        }
+    }
+
+    private String getStringTerm(String term) throws DescriptorParsingException {
+        String errorMsg = 
+                "Expected string value. Got term ->"
+                        + term
+                        + "<-"
+                        + " Was the string properly quoted? Example: 'string'.";
+        if (term.charAt(0) != '\'' || term.charAt(term.length() - 1) != '\'') {
+            throw new DescriptorParsingException(errorMsg);
+        }
+        return term.substring(1, term.length() - 1);
+    }
+
+    private FreeVarTypeToken getType(String term) {
+        if (term.equals("")) {
+            // illegal type
+            return null;
+        }
+        // free variable types can have 1 or 2 characters.
+        assert(term.length() > 0 && term.length() < 3);
+        char switchChar = term.charAt(0);
+        FreeVarTypeToken typeToken = null;
+        switch (switchChar) {
+        case 'i': {
+            if (term.length() == 1) {
+                typeToken = new FreeVarTypeToken(Integer.class, false);
+            } else if (term.length() == 2 && term.charAt(1) == '[') {
+                typeToken = new FreeVarTypeToken(Integer.class, true);
+            }
+            break;
+        }
+        case 'l': {
+            if (term.length() == 1) {
+                typeToken = new FreeVarTypeToken(Long.class, false);
+            } else if (term.length() == 2 && term.charAt(1) == '[') {
+                typeToken = new FreeVarTypeToken(Long.class, true);
+            }
+            break;
+        }
+        case 's': {
+            if (term.length() == 1) {
+                typeToken = new FreeVarTypeToken(String.class, false);
+            } else if (term.length() == 2 && term.charAt(1) == '[') {
+                typeToken = new FreeVarTypeToken(String.class, true);
+            }
+            break;
+        }
+        case 'b': {
+            if (term.length() == 1) {
+                typeToken = new FreeVarTypeToken(Boolean.class, false);
+            } else if (term.length() == 2 && term.charAt(1) == '[') {
+                typeToken = new FreeVarTypeToken(Boolean.class, true);
+            }
+            break;
+        }
+        case 'd': {
+            if (term.length() == 1) {
+                typeToken = new FreeVarTypeToken(Double.class, false);
+            } else if (term.length() == 2 && term.charAt(1) == '[') {
+                typeToken = new FreeVarTypeToken(Double.class, true);
+            }
+            break;
+        }
+        case 'p': {
+            if (term.length() == 1) {
+                typeToken = new FreeVarTypeToken(Pojo.class, false);
+            } else if (term.length() == 2 && term.charAt(1) == '[') {
+                typeToken = new FreeVarTypeToken(Pojo.class, true);
+            }
+            break;
+        }
+        default:
+            assert(typeToken == null);
+            break;
+        }
+        return typeToken;
+    }
+
+    private String getTerm() throws DescriptorParsingException {
+        if (currTokenIndex >= tokens.length) {
+            throw new DescriptorParsingException("Invalid where clause. Reason: term expected but not given!");
+        }
+        if (tokens[currTokenIndex].charAt(0) == PARAM_PLACEHOLDER) {
+                placeHolderCount++;
+        }
+        String term = tokens[currTokenIndex];
+        currTokenIndex++;
+        return term;
+    }
+
+    private void matchAndExpression(Node currNode) throws DescriptorParsingException {
+        if (currTokenIndex < tokens.length &&
+                tokens[currTokenIndex].equals(Operator.AND.getName())) {
+            currTokenIndex++; // AND keyword
+            
+            Node parent = currNode;
+            if (currNode instanceof BinaryExpressionNode ||
+                    currNode instanceof NotBooleanExpressionNode) {
+                parent = currNode.getParent();
+                assert(parent != null);
+            }
+            BinaryExpressionNode and = new BinaryExpressionNode(parent);
+            and.setOperator(BinaryLogicalOperator.AND);
+            if (currNode instanceof BinaryExpressionNode ||
+                    currNode instanceof NotBooleanExpressionNode) {
+                currNode.setParent(and);
+                and.setLeftChild(currNode);
+                parent.setValue(and);
+            } else {
+                // Root node case
+                and.setLeftChild((Node)parent.getValue());
+                parent.setValue(and);
+            }
+            // Note the current AND expression node at this point of parsing
+            // must be at the root of the entire expression.
+            assert(and.getParent().getParent() == null);
+            
+            matchWhereExp(and);
+            
+        }
+        // empty
+    }
+
+    private void matchOrCondition(Node currNode) throws DescriptorParsingException {
+        if (currTokenIndex < tokens.length &&
+                tokens[currTokenIndex].equals(Operator.OR.getName())) {
+            currTokenIndex++; // OR keyword
+            
+            Node parent = currNode;
+            if (currNode instanceof BinaryExpressionNode ||
+                    currNode instanceof NotBooleanExpressionNode) {
+                parent = currNode.getParent();
+                assert(parent != null);
+            }
+            BinaryExpressionNode or = new BinaryExpressionNode(parent);
+            or.setOperator(BinaryLogicalOperator.OR);
+            if (currNode instanceof BinaryExpressionNode ||
+                    currNode instanceof NotBooleanExpressionNode) {
+                currNode.setParent(or);
+                or.setLeftChild(currNode);
+                parent.setValue(or);
+            } else {
+                // Root node case
+                or.setLeftChild((Node)parent.getValue());
+                parent.setValue(or);
+            }
+            // Note the current OR expression node at this point of parsing
+            // must be at the root of the entire expression.
+            assert(or.getParent().getParent() == null);
+            
+            matchWhereExp(or);
+        }
+        // empty
+    }
+
+    private void createStatement() {
+        // matchStatementType and matchCategory advanced currTokenIndex,
+        // lets use idx of 0 here.
+        final String statementType = tokens[0];
+        if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) {
+            // regular query case
+            Query<T> query = storage.createQuery(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(query);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY_COUNT])) {
+            // create aggregate count query
+            Query<T> query = storage.createAggregateQuery(AggregateFunction.COUNT, desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(query);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) {
+            // create add
+            Add<T> add = storage.createAdd(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(add);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) {
+            // create replace
+            Replace<T> replace = storage.createReplace(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(replace);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) {
+            // create replace
+            Update<T> update = storage.createUpdate(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(update);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) {
+            // create remove
+            Remove<T> remove = storage.createRemove(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(remove);
+        } else {
+            throw new IllegalStateException("Don't know how to create statement type '" + statementType + "'");
+        }
+    }
+
+    private void matchCategory() throws DescriptorParsingException {
+        if (currTokenIndex >= tokens.length) {
+            throw new DescriptorParsingException("Missing category name in descriptor: '" + desc.getDescriptor() + "'");
+        }
+        Category<?> category = desc.getCategory();
+        if (!tokens[currTokenIndex].equals(category.getName())) {
+            throw new DescriptorParsingException(
+                    "Category mismatch in descriptor. Category from descriptor string: '"
+                            + tokens[currTokenIndex]
+                            + "'. Category name from category: '"
+                            + category.getName() + "'.");
+        }
+        currTokenIndex++;
+    }
+
+    private void matchStatementType() throws DescriptorParsingException {
+        if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) {
+            // QUERY
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_QUERY_COUNT])) {
+            // QUERY-COUNT
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) {
+            // ADD
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) {
+            // REPLACE
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) {
+            // UPDATE
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) {
+            // REMOVE
+            currTokenIndex++;
+        } else {
+            throw new DescriptorParsingException("Unknown statement type: '" + tokens[currTokenIndex] + "'");
+        }
+    }
+    
+    private static class FreeVarTypeToken {
+        
+        private final boolean isArrayType;
+        private final Class<?> componentType;
+        
+        private FreeVarTypeToken(Class<?> componentType, boolean isArrayType) {
+            this.isArrayType = isArrayType;
+            this.componentType = componentType;
+        }
+    }
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java	Mon Oct 07 15:03:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java	Fri Oct 04 18:13:31 2013 +0200
@@ -49,7 +49,7 @@
 import com.redhat.thermostat.storage.query.Expression;
 
 /**
- * Result object as returned by {@link StatementDescriptorParser#parse()}.
+ * Result object as returned by {@link BasicDescriptorParser#parse()}.
  * An instance of this plus a list of {@link PreparedParameter} should
  * be sufficient to patch up a prepared statement with its real values.
  *
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Mon Oct 07 15:03:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Fri Oct 04 18:13:31 2013 +0200
@@ -65,7 +65,7 @@
     
     public PreparedStatementImpl(BackingStorage storage, StatementDescriptor<T> desc) throws DescriptorParsingException {
         this.desc = desc;
-        StatementDescriptorParser<T> parser = new StatementDescriptorParser<>(storage, desc);
+        BasicDescriptorParser<T> parser = new BasicDescriptorParser<>(storage, desc);
         this.parsedStatement = (ParsedStatementImpl<T>)parser.parse();
         int numParams = parsedStatement.getNumParams();
         params = new PreparedParameters(numParams);
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Mon Oct 07 15:03:04 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,887 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.storage.internal.statement;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
-
-import com.redhat.thermostat.storage.core.Add;
-import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction;
-import com.redhat.thermostat.storage.core.BackingStorage;
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.DescriptorParsingException;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.ParsedStatement;
-import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Query.SortDirection;
-import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.Replace;
-import com.redhat.thermostat.storage.core.Statement;
-import com.redhat.thermostat.storage.core.StatementDescriptor;
-import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
-import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
-
-/**
- * A parser for the string representation of {@link StatementDescriptor}s.
- * Tokens have to be separated by whitespace.
- * 
- * This parser implements the following simple grammar for statement descriptors.
- * It supports the following statement types:
- * <ul>
- * <li>QUERY (read)</li>
- * <li>QUERY-COUNT (read)</li>
- * <li>ADD (write)</li>
- * <li>UPDATE (write)</li>
- * <li>REPLACE (write)</li>
- * <li>REMOVE (write)</li>
- * </ul>
- * 
- * <p><strong>Grammar:</strong></p>
- * <pre>
- * statementDesc := statementType category setList suffix
- * statementType := 'QUERY' | 'QUERY-COUNT' |
- *                  'ADD' | 'REPLACE' | 'UPDATE' |
- *                  'REMOVE'
- * category      := string
- * setList       := 'SET' setValues | \empty
- * setValues     := valuePair valueList
- * valuePair     := term '=' term
- * valueList     := ',' setValues | \empty
- * suffix        := 'WHERE' where |
- *                  'SORT' sortCond |
- *                  'LIMIT' term | \empty
- * where         := whereExp sort limit
- * whereExp      := andCond orCond
- * orCond        := 'OR' whereExp | \empty
- * sort          := 'SORT' sortCond | \empty
- * sortCond      := sortPair sortList
- * sortPair      := term sortModifier
- * sortModifier  := 'ASC' | 'DSC'
- * sortList      := ',' sortCond | \empty
- * limit         := 'LIMIT' term | \empty
- * andCond       := condition andBody
- * andBody       := 'AND' whereExp | \empty
- * condition     := 'NOT' condition | compExp
- * compExp       := term compExpRHS
- * term          := freeParam | literal
- * freeParam     := '?s' | '?i' | '?l' | '?s[' | '?b'
- * literal       := sQuote string sQuote | int | long | boolean
- * sQuote        := \'
- * boolean       := &lt;true&gt; | &lt;false&gt;
- * int           := &lt;literal-int&gt;
- * long          := &lt;literal-long&gt;longPostFix
- * longPostFix   := 'l' | 'L'
- * string        := &lt;literal-string-value&gt;
- * compExpRHS    := '!=' term | '=' term | '&lt;=' term | '&gt;=' term | 
- *                  '&lt;' term | '&gt;' term
- * </pre>
- *
- * This implements the following logic precedence rules (in this order of
- * precedence):
- * 
- * <ol>
- *   <li>NOT</li>
- *   <li>AND</li>
- *   <li>OR</li>
- * </ol>
- * 
- * NOTE: Comparison expressions have equal precedence.
- */
-class StatementDescriptorParser<T extends Pojo> {
-
-    private static final String TOKEN_DELIMS = " \t\r\n\f";
-    private static final short IDX_QUERY = 0;
-    private static final short IDX_QUERY_COUNT = 1;
-    private static final short IDX_ADD = 2;
-    private static final short IDX_REPLACE = 3;
-    private static final short IDX_UPDATE = 4;
-    private static final short IDX_REMOVE = 5;
-    private static final String[] KNOWN_STATEMENT_TYPES = new String[] {
-        "QUERY", "QUERY-COUNT", "ADD", "REPLACE", "UPDATE", "REMOVE"
-    };
-    private static final String SORTLIST_SEP = ",";
-    private static final String SETLIST_SEP = SORTLIST_SEP;
-    private static final String KEYWORD_SET = "SET";
-    private static final String KEYWORD_WHERE = "WHERE";
-    private static final String KEYWORD_SORT = "SORT";
-    private static final String KEYWORD_LIMIT = "LIMIT";
-    private static final String KEYWORD_ASC = "ASC";
-    private static final String KEYWORD_DSC = "DSC";
-    private static final String POJO_FREE_PARAMETER_TYPE = "?p";
-    private static final char PARAM_PLACEHOLDER = '?';
-    
-    private final String[] tokens;
-    private final StatementDescriptor<T> desc;
-    private final BackingStorage storage;
-    private int currTokenIndex;
-    private int placeHolderCount;
-    // the parsed statement
-    private ParsedStatementImpl<T> parsedStatement;
-    private SuffixExpression tree;
-    private SetList setList;
-    
-    StatementDescriptorParser(BackingStorage storage, StatementDescriptor<T> desc) {
-        this.tokens = getTokens(desc.getDescriptor());
-        this.currTokenIndex = 0;
-        this.placeHolderCount = 0;
-        this.desc = desc;
-        this.storage = storage;
-    }
-    
-    private String[] getTokens(String str) {
-        StringTokenizer tokenizer = new StringTokenizer(str, TOKEN_DELIMS);
-        List<String> toks = new ArrayList<>(tokenizer.countTokens());
-        while (tokenizer.hasMoreTokens()) {
-            toks.add(tokenizer.nextToken());
-        }
-        return toks.toArray(new String[0]);
-    }
-
-    public ParsedStatement<T> parse() throws DescriptorParsingException {
-        matchStatementType();
-        matchCategory();
-        // matched so far, create the raw statement
-        createStatement();
-        this.setList = new SetList();
-        matchSetList(setList);
-        this.tree = new SuffixExpression();
-        matchSuffix();
-        if (currTokenIndex != tokens.length) {
-            throw new DescriptorParsingException("Incomplete parse");
-        }
-        parsedStatement.setNumFreeParams(placeHolderCount);
-        parsedStatement.setSetList(setList);
-        parsedStatement.setSuffixExpression(tree);
-        doSemanticAnalysis();
-        return parsedStatement;
-    }
-
-    private void doSemanticAnalysis() throws DescriptorParsingException {
-        // TODO:
-        // - Check that ADD/REPLACE specifies all keys judging by the Pojo
-        //   model class. Not sure if good idea though, as this would likely
-        //   introduce dep on beanutils.
-        Statement<T> stmt = parsedStatement.getRawStatement();
-        if (stmt == null) {
-            // should never be null
-            throw new NullPointerException();
-        }
-        if (stmt instanceof Add && tree.getWhereExpn() != null) {
-            String msg = "WHERE clause not allowed for ADD";
-            throw new DescriptorParsingException(msg);
-        }
-        if (stmt instanceof Replace && tree.getWhereExpn() == null) {
-            String msg = "WHERE clause required for REPLACE";
-            throw new DescriptorParsingException(msg);
-        }
-        if (stmt instanceof Update) {
-            if (tree.getWhereExpn() == null) {
-                // WHERE required for UPDATE
-                String msg = "WHERE clause required for UPDATE";
-                throw new DescriptorParsingException(msg);
-            }
-            if (setList.getValues().size() == 0) {
-                // SET required for UPDATE
-                String msg = "SET list required for UPDATE";
-                throw new DescriptorParsingException(msg);
-            }
-        }
-        if (stmt instanceof Remove && setList.getValues().size() > 0) {
-            String msg = "SET not allowed for REMOVE";
-            throw new DescriptorParsingException(msg);
-        }
-        if (stmt instanceof Query) {
-            if (setList.getValues().size() > 0) {
-                // Must not have SET for QUERYs
-                String msg = "SET not allowed for QUERY/QUERY-COUNT";
-                throw new DescriptorParsingException(msg);
-            }
-        } else {
-            // only queries can have sort/limit expressions
-            if (this.tree.getLimitExpn() != null || this.tree.getSortExpn() != null) {
-                String msg = "LIMIT/SORT only allowed for QUERY/QUERY-COUNT";
-                throw new DescriptorParsingException(msg);
-            }
-        }
-    }
-
-    /*
-     * Match set list for DML statements.
-     */
-    private void matchSetList(final SetList setList) throws DescriptorParsingException {
-        if (tokens.length == currTokenIndex) {
-            // no set list
-            return;
-        }
-        if (tokens[currTokenIndex].equals(KEYWORD_SET)) {
-            currTokenIndex++; // SET
-            matchSetValues(setList);
-        }
-        // empty, proceed with suffix
-    }
-
-    /*
-     * Match list of values in a SET expression 
-     */
-    private void matchSetValues(SetList setList) throws DescriptorParsingException {
-        matchValuePair(setList);
-        matchValueList(setList);
-    }
-
-    /*
-     * Match more value pairs in a SET list
-     */
-    private void matchValueList(SetList setList) throws DescriptorParsingException {
-        if (currTokenIndex == tokens.length) {
-            // empty
-            return;
-        }
-        if (tokens[currTokenIndex].equals(SETLIST_SEP)) {
-            currTokenIndex++; // ,
-            matchSetValues(setList);
-        }
-    }
-
-    /*
-     * Match one pair of values in a 
-     */
-    private void matchValuePair(SetList setList) throws DescriptorParsingException {
-        SetListValue value = new SetListValue();
-        TerminalNode lval = new TerminalNode(null);
-        matchTerm(lval, true, true);
-        value.setKey(lval);
-        if (tokens[currTokenIndex].equals("=")) {
-            currTokenIndex++; // =
-        } else {
-            String msg = "Expected '=' after SET value LHS. Token was ->" + tokens[currTokenIndex] + "<-";
-            throw new DescriptorParsingException(msg);
-        }
-        TerminalNode rval = new TerminalNode(null);
-        matchTerm(rval, false, true);
-        value.setValue(rval);
-        setList.addValue(value);
-    }
-
-    /*
-     * Match optional suffixes. 
-     */
-    private void matchSuffix() throws DescriptorParsingException {
-        if (tokens.length == currTokenIndex) {
-            // no suffix
-            return;
-        }
-        if (tokens[currTokenIndex].equals(KEYWORD_WHERE)) {
-            currTokenIndex++;
-            WhereExpression expn = new WhereExpression();
-            tree.setWhereExpn(expn);
-            matchWhereExp(expn.getRoot());
-            matchSort(tree);
-            matchLimit(tree);
-        } else if (tokens[currTokenIndex].equals(KEYWORD_SORT)) {
-            // SORT token eaten up by matchSort()
-            matchSort(tree);
-            matchLimit(tree);
-        } else if (tokens[currTokenIndex].equals(KEYWORD_LIMIT)) {
-            // LIMIT token eaten up by matchLimit()
-            matchLimit(tree);
-        } else {
-            throw new DescriptorParsingException("Unexpected token: '"
-                    + tokens[currTokenIndex] + "'. Expected one of "
-                    + KEYWORD_WHERE + ", " + KEYWORD_SORT + ", " + KEYWORD_LIMIT);
-        }
-    }
-
-    private void matchLimit(SuffixExpression tree) throws DescriptorParsingException {
-        if (currTokenIndex == tokens.length) {
-            // empty
-            return;
-        } else if (currTokenIndex < tokens.length) {
-            if (tokens[currTokenIndex].equals(KEYWORD_LIMIT)) {
-                LimitExpression node = new LimitExpression();
-                tree.setLimitExpn(node);
-                currTokenIndex++;
-                matchTerm(node);
-            }
-        } else {
-            throw new DescriptorParsingException("Illegal statement descriptor: Reason LIMIT");
-        }
-    }
-
-    private void matchSort(SuffixExpression tree) throws DescriptorParsingException {
-        if (currTokenIndex < tokens.length
-                && tokens[currTokenIndex].equals(KEYWORD_SORT)) {
-            SortExpression sortExpn = new SortExpression();
-            tree.setSortExpn(sortExpn);
-            currTokenIndex++;
-            matchSortList(sortExpn);
-        } 
-        if (currTokenIndex > tokens.length) {
-            throw new DescriptorParsingException("Illegal statement descriptor.");
-        }
-        // empty
-    }
-
-    private void matchSortList(SortExpression sortExpn) throws DescriptorParsingException {
-        matchSortPair(sortExpn);
-        matchSortListPreamble(sortExpn);
-    }
-
-    private void matchSortListPreamble(SortExpression sortExpn) throws DescriptorParsingException {
-        if (currTokenIndex < tokens.length && tokens[currTokenIndex].equals(SORTLIST_SEP)) {
-            currTokenIndex++; // ',' token
-            matchSortList(sortExpn);
-        }
-    }
-
-    private void matchSortPair(SortExpression expn) throws DescriptorParsingException {
-        SortMember member = new SortMember();
-        matchTerm(member);
-        matchSortModifier(member);
-        // Add the member node to the list of the sort node
-        expn.addMember(member);
-    }
-
-    private void matchSortModifier(SortMember member) throws DescriptorParsingException {
-        String msg = "Illegal statement decriptor: Reason SORT. Expected ASC or DSC";
-        if (currTokenIndex >= tokens.length) {
-            throw new DescriptorParsingException(msg);
-        }
-        if (tokens[currTokenIndex].equals(KEYWORD_ASC)) {
-            member.setDirection(SortDirection.ASCENDING);
-            currTokenIndex++;
-        } else if (tokens[currTokenIndex].equals(KEYWORD_DSC)) {
-            member.setDirection(SortDirection.DESCENDING);
-            currTokenIndex++;
-        } else {
-            throw new DescriptorParsingException(msg);
-        }
-    }
-
-    private void matchWhereExp(Node node) throws DescriptorParsingException {
-        if (currTokenIndex >= tokens.length) {
-            throw new DescriptorParsingException("Illegal where clause");
-        }
-        assert(node != null);
-        matchAndCondition(node);
-        matchOrCondition(node);
-    }
-
-    private void matchAndCondition(Node currNode) throws DescriptorParsingException {
-        matchCondition(currNode);
-        matchAndExpression(currNode);
-    }
-
-    private void matchCondition(Node currNode) throws DescriptorParsingException {
-        if (currTokenIndex >= tokens.length) {
-            throw new DescriptorParsingException("Illegal statement descriptor: Reason sort clause");
-        }
-        if (tokens[currTokenIndex].equals(Operator.NOT.getName())) {
-            NotBooleanExpressionNode notNode = new NotBooleanExpressionNode(currNode);
-            if (currNode instanceof BinaryExpressionNode) {
-                BinaryExpressionNode currNodeExpr = (BinaryExpressionNode)currNode;
-                Node available = currNodeExpr.getLeftChild();
-                if (available != null) {
-                    currNodeExpr.setRightChild(notNode);
-                } else {
-                    currNodeExpr.setLeftChild(notNode);
-                }
-            } else {
-                assert(currNode instanceof NotBooleanExpressionNode || currNode instanceof Node);
-                currNode.setValue(notNode);
-            }
-            currTokenIndex++; // NOT keyword
-            
-            matchCondition(notNode);
-        } else {
-            matchComparisionExpression(currNode);
-        }
-    }
-
-    private void matchComparisionExpression(Node currNode) throws DescriptorParsingException {
-        if (currTokenIndex >= tokens.length) {
-            throw new DescriptorParsingException("Illegal statement descriptor: Comparison expression");
-        }
-        BinaryExpressionNode expr = new BinaryExpressionNode(currNode);
-        TerminalNode left = new TerminalNode(expr);
-        TerminalNode right = new TerminalNode(expr);
-        expr.setLeftChild(left);
-        expr.setRightChild(right);
-        
-        if (currNode instanceof BinaryExpressionNode) {
-            BinaryExpressionNode currNodeExpr = (BinaryExpressionNode)currNode;
-            Node available = currNodeExpr.getLeftChild();
-            if (available == null) {
-                currNodeExpr.setLeftChild(expr);
-            } else {
-                assert(currNodeExpr.getRightChild() == null);
-                currNodeExpr.setRightChild(expr);
-            }
-        } else {
-            assert(currNode instanceof NotBooleanExpressionNode || currNode instanceof Node);
-            currNode.setValue(expr);
-        }
-        
-        matchTerm(left, true);
-        matchComparisonRHS(expr);
-    }
-
-    private void matchComparisonRHS(BinaryExpressionNode currNode) throws DescriptorParsingException {
-        if (currTokenIndex >= tokens.length) {
-            // boolean literals are not allowed
-            throw new DescriptorParsingException("Illegal statement descriptor: Boolean literals are not allowed!");
-        }
-        if (tokens[currTokenIndex].equals(Operator.EQUALS.getName())) {
-            currTokenIndex++;
-            currNode.setOperator(BinaryComparisonOperator.EQUALS);
-            matchTerm((TerminalNode)currNode.getRightChild(), false);
-        } else if (tokens[currTokenIndex].equals(Operator.LESS_THAN_OR_EQUAL_TO.getName())) {
-            currTokenIndex++;
-            currNode.setOperator(BinaryComparisonOperator.LESS_THAN_OR_EQUAL_TO);
-            matchTerm((TerminalNode)currNode.getRightChild(), false);
-        } else if (tokens[currTokenIndex].equals(Operator.GREATER_THAN_OR_EQUAL_TO.getName())) {
-            currTokenIndex++;
-            currNode.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
-            matchTerm((TerminalNode)currNode.getRightChild(), false);
-        } else if (tokens[currTokenIndex].equals(Operator.GREATER_THAN.getName())) {
-            currTokenIndex++;
-            currNode.setOperator(BinaryComparisonOperator.GREATER_THAN);
-            matchTerm((TerminalNode)currNode.getRightChild(), false);
-        } else if (tokens[currTokenIndex].equals(Operator.LESS_THAN.getName())) {
-            currTokenIndex++;
-            currNode.setOperator(BinaryComparisonOperator.LESS_THAN);
-            matchTerm((TerminalNode)currNode.getRightChild(), false);
-        } else if (tokens[currTokenIndex].equals(Operator.NOT_EQUAL_TO.getName())) {
-            currTokenIndex++;
-            currNode.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
-            matchTerm((TerminalNode)currNode.getRightChild(), false);
-        } else {
-            throw new DescriptorParsingException("Illegal statement descriptor: Reason comparison expression!");
-        }
-    }
-    
-    private void matchTerm(SortMember member) throws DescriptorParsingException {
-        String term = getTerm();
-        if (term.charAt(0) == PARAM_PLACEHOLDER) {
-            assert(placeHolderCount > 0);
-            ensureValidType(term, "SORT");
-            if (term.charAt(1) != 's') {
-                String msg = "Sort parameters only accept string types. Placeholder was: " + term;
-                throw new DescriptorParsingException(msg);
-            }
-            UnfinishedSortKey unfinishedKey = new UnfinishedSortKey();
-            unfinishedKey.setParameterIndex(placeHolderCount - 1);
-            member.setSortKey(unfinishedKey);
-            return;
-        }
-        String stringTerm = getStringTerm(term);
-        member.setSortKey(stringTerm);
-    }
-    
-    /*
-     * Check if the free parameter type is valid in a given context. Currently,
-     * list and pojo free type parameters are invalid for LIMIT, SORT and WHERE.
-     *  
-     * Currently, no list types and no Pojo types are allowed.
-     */
-    private void ensureValidType(String term, String contextName) throws DescriptorParsingException {
-        if (term.length() > 2) {
-            // Don't allow list types for invalid contexts
-            // Only list type free variables have 3 characters
-            String format = "List free variable type not allowed in %s context";
-            String msg = String.format(format, contextName);
-            throw new DescriptorParsingException(msg);
-        }
-        if (term.equals(POJO_FREE_PARAMETER_TYPE)) {
-            String format = "Pojo free variable type not allowed in %s context";
-            String msg = String.format(format, contextName);
-            throw new DescriptorParsingException(msg);
-        }
-    }
-    
-    private void matchTerm(LimitExpression expn) throws DescriptorParsingException {
-        String term = getTerm();
-        if (term.charAt(0) == PARAM_PLACEHOLDER) {
-            assert(placeHolderCount > 0);
-            ensureValidType(term, "LIMIT");
-            if (term.charAt(1) != 'i') {
-                String msg = "Limit parameters only accept integer types. Placeholder was: " + term;
-                throw new DescriptorParsingException(msg);
-            }
-            UnfinishedLimitValue limitValue = new UnfinishedLimitValue();
-            limitValue.setParameterIndex(placeHolderCount - 1);
-            expn.setValue(limitValue);
-            return;
-        }
-        int limitVal;
-        try {
-            limitVal = Integer.parseInt(term);
-        } catch (NumberFormatException e) {
-            throw new DescriptorParsingException("Invalid limit expression. '" + term + "' not an integer");
-        }
-        expn.setValue(limitVal);
-    }
-    
-    /**
-     * Calls {@link #matchTerm(TerminalNode, boolean, boolean)} with a
-     * {@code false isSetListContext} parameter.
-     */
-    private void matchTerm(TerminalNode node, boolean isLHS) throws DescriptorParsingException {
-        // default to false
-        matchTerm(node, isLHS, false);
-    }
-
-    /**
-     * Match a terminal in a TerminalNode context. Only where expression, and
-     * set list context take this code path.
-     * 
-     * @param node
-     *            The terminal node which is parsed at this point.
-     * @param isLHS
-     *            {@code true} if and only if the node is the left hand side of
-     *            a binary expression.
-     * @param isSetListContext
-     *            {@code true} if and only if the node is in a set list context
-     *            or conversely NOT in a where expression context.
-     * @throws DescriptorParsingException
-     *             If and error was encountered parsing the terminal string
-     *             token.
-     */
-    private void matchTerm(TerminalNode node, boolean isLHS,
-            boolean isSetListContext) throws DescriptorParsingException {
-        String term = getTerm();
-        if (term.charAt(0) == PARAM_PLACEHOLDER) {
-            assert(placeHolderCount > 0);
-            if (!isSetListContext) {
-                ensureValidType(term, "WHERE");
-            }
-            UnfinishedValueNode patchNode = new UnfinishedValueNode();
-            patchNode.setParameterIndex(placeHolderCount - 1);
-            patchNode.setLHS(isLHS);
-            // figure out the expected type
-            FreeVarTypeToken expectedType = getType(term.substring(1));
-            if (expectedType == null) {
-                throw new DescriptorParsingException("Unknown type of free parameter: '" + term + "'");
-            }
-            patchNode.setType(expectedType.componentType);
-            patchNode.setArrayType(expectedType.isArrayType);
-            node.setValue(patchNode);
-            return;
-        }
-        // regular terminal. i.e. literal value
-        if (isLHS) {
-            // FIXME: In thermostat LHS of comparisons must be Key objects. I'm
-            // not sure if this restriction is very meaningful in a prepared
-            // statement context as the purpose of this was to ensure "type"
-            // compatibility between Key <=> value comparisons.
-            String stringTerm = getStringTerm(term);
-            Key<?> key = new Key<>(stringTerm);
-            node.setValue(key);
-        } else {
-            Object typedValue = getTypedValue(term);
-            node.setValue(typedValue);
-        }
-    }
-    
-    private Object getTypedValue(String term) throws DescriptorParsingException {
-        try {
-            String stringTerm = getStringTerm(term);
-            return stringTerm;
-        } catch (DescriptorParsingException e) {
-            // Must be integer (long/int) or boolean. First check for boolean,
-            // then for long and regular ints.
-            if (term.equals(Boolean.toString(false)) || term.equals(Boolean.toString(true))) {
-                boolean boolVal = Boolean.parseBoolean(term);
-                return boolVal;
-            }
-            // Next, parse long or int.
-            try {
-                int lastCharInTokenIndex = term.length() - 1;
-                // preceding l/L indicate long integer types.
-                if (term.charAt(lastCharInTokenIndex) == 'L' || term.charAt(lastCharInTokenIndex) == 'l') {
-                    long longVal = Long.parseLong(term.substring(0, lastCharInTokenIndex));
-                    return longVal;
-                }
-                // must be integer or some invalid type
-                int intVal = Integer.parseInt(term);
-                return intVal;
-            } catch (NumberFormatException nfe) {
-                String msg = "Illegal terminal type. Token was ->" + term + "<-";
-                throw new DescriptorParsingException(msg);
-            }
-        }
-    }
-
-    private String getStringTerm(String term) throws DescriptorParsingException {
-        String errorMsg = 
-                "Expected string value. Got term ->"
-                        + term
-                        + "<-"
-                        + " Was the string properly quoted? Example: 'string'.";
-        if (term.charAt(0) != '\'' || term.charAt(term.length() - 1) != '\'') {
-            throw new DescriptorParsingException(errorMsg);
-        }
-        return term.substring(1, term.length() - 1);
-    }
-
-    private FreeVarTypeToken getType(String term) {
-        if (term.equals("")) {
-            // illegal type
-            return null;
-        }
-        // free variable types can have 1 or 2 characters.
-        assert(term.length() > 0 && term.length() < 3);
-        char switchChar = term.charAt(0);
-        FreeVarTypeToken typeToken = null;
-        switch (switchChar) {
-        case 'i': {
-            if (term.length() == 1) {
-                typeToken = new FreeVarTypeToken(Integer.class, false);
-            } else if (term.length() == 2 && term.charAt(1) == '[') {
-                typeToken = new FreeVarTypeToken(Integer.class, true);
-            }
-            break;
-        }
-        case 'l': {
-            if (term.length() == 1) {
-                typeToken = new FreeVarTypeToken(Long.class, false);
-            } else if (term.length() == 2 && term.charAt(1) == '[') {
-                typeToken = new FreeVarTypeToken(Long.class, true);
-            }
-            break;
-        }
-        case 's': {
-            if (term.length() == 1) {
-                typeToken = new FreeVarTypeToken(String.class, false);
-            } else if (term.length() == 2 && term.charAt(1) == '[') {
-                typeToken = new FreeVarTypeToken(String.class, true);
-            }
-            break;
-        }
-        case 'b': {
-            if (term.length() == 1) {
-                typeToken = new FreeVarTypeToken(Boolean.class, false);
-            } else if (term.length() == 2 && term.charAt(1) == '[') {
-                typeToken = new FreeVarTypeToken(Boolean.class, true);
-            }
-            break;
-        }
-        case 'd': {
-            if (term.length() == 1) {
-                typeToken = new FreeVarTypeToken(Double.class, false);
-            } else if (term.length() == 2 && term.charAt(1) == '[') {
-                typeToken = new FreeVarTypeToken(Double.class, true);
-            }
-            break;
-        }
-        case 'p': {
-            if (term.length() == 1) {
-                typeToken = new FreeVarTypeToken(Pojo.class, false);
-            } else if (term.length() == 2 && term.charAt(1) == '[') {
-                typeToken = new FreeVarTypeToken(Pojo.class, true);
-            }
-            break;
-        }
-        default:
-            assert(typeToken == null);
-            break;
-        }
-        return typeToken;
-    }
-
-    private String getTerm() throws DescriptorParsingException {
-        if (currTokenIndex >= tokens.length) {
-            throw new DescriptorParsingException("Invalid where clause. Reason: term expected but not given!");
-        }
-        if (tokens[currTokenIndex].charAt(0) == PARAM_PLACEHOLDER) {
-                placeHolderCount++;
-        }
-        String term = tokens[currTokenIndex];
-        currTokenIndex++;
-        return term;
-    }
-
-    private void matchAndExpression(Node currNode) throws DescriptorParsingException {
-        if (currTokenIndex < tokens.length &&
-                tokens[currTokenIndex].equals(Operator.AND.getName())) {
-            currTokenIndex++; // AND keyword
-            
-            Node parent = currNode;
-            if (currNode instanceof BinaryExpressionNode ||
-                    currNode instanceof NotBooleanExpressionNode) {
-                parent = currNode.getParent();
-                assert(parent != null);
-            }
-            BinaryExpressionNode and = new BinaryExpressionNode(parent);
-            and.setOperator(BinaryLogicalOperator.AND);
-            if (currNode instanceof BinaryExpressionNode ||
-                    currNode instanceof NotBooleanExpressionNode) {
-                currNode.setParent(and);
-                and.setLeftChild(currNode);
-                parent.setValue(and);
-            } else {
-                // Root node case
-                and.setLeftChild((Node)parent.getValue());
-                parent.setValue(and);
-            }
-            // Note the current AND expression node at this point of parsing
-            // must be at the root of the entire expression.
-            assert(and.getParent().getParent() == null);
-            
-            matchWhereExp(and);
-            
-        }
-        // empty
-    }
-
-    private void matchOrCondition(Node currNode) throws DescriptorParsingException {
-        if (currTokenIndex < tokens.length &&
-                tokens[currTokenIndex].equals(Operator.OR.getName())) {
-            currTokenIndex++; // OR keyword
-            
-            Node parent = currNode;
-            if (currNode instanceof BinaryExpressionNode ||
-                    currNode instanceof NotBooleanExpressionNode) {
-                parent = currNode.getParent();
-                assert(parent != null);
-            }
-            BinaryExpressionNode or = new BinaryExpressionNode(parent);
-            or.setOperator(BinaryLogicalOperator.OR);
-            if (currNode instanceof BinaryExpressionNode ||
-                    currNode instanceof NotBooleanExpressionNode) {
-                currNode.setParent(or);
-                or.setLeftChild(currNode);
-                parent.setValue(or);
-            } else {
-                // Root node case
-                or.setLeftChild((Node)parent.getValue());
-                parent.setValue(or);
-            }
-            // Note the current OR expression node at this point of parsing
-            // must be at the root of the entire expression.
-            assert(or.getParent().getParent() == null);
-            
-            matchWhereExp(or);
-        }
-        // empty
-    }
-
-    private void createStatement() {
-        // matchStatementType and matchCategory advanced currTokenIndex,
-        // lets use idx of 0 here.
-        final String statementType = tokens[0];
-        if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) {
-            // regular query case
-            Query<T> query = storage.createQuery(desc.getCategory());
-            this.parsedStatement = new ParsedStatementImpl<>(query);
-        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY_COUNT])) {
-            // create aggregate count query
-            Query<T> query = storage.createAggregateQuery(AggregateFunction.COUNT, desc.getCategory());
-            this.parsedStatement = new ParsedStatementImpl<>(query);
-        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) {
-            // create add
-            Add<T> add = storage.createAdd(desc.getCategory());
-            this.parsedStatement = new ParsedStatementImpl<>(add);
-        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) {
-            // create replace
-            Replace<T> replace = storage.createReplace(desc.getCategory());
-            this.parsedStatement = new ParsedStatementImpl<>(replace);
-        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) {
-            // create replace
-            Update<T> update = storage.createUpdate(desc.getCategory());
-            this.parsedStatement = new ParsedStatementImpl<>(update);
-        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) {
-            // create remove
-            Remove<T> remove = storage.createRemove(desc.getCategory());
-            this.parsedStatement = new ParsedStatementImpl<>(remove);
-        } else {
-            throw new IllegalStateException("Don't know how to create statement type '" + statementType + "'");
-        }
-    }
-
-    private void matchCategory() throws DescriptorParsingException {
-        if (currTokenIndex >= tokens.length) {
-            throw new DescriptorParsingException("Missing category name in descriptor: '" + desc.getDescriptor() + "'");
-        }
-        Category<?> category = desc.getCategory();
-        if (!tokens[currTokenIndex].equals(category.getName())) {
-            throw new DescriptorParsingException(
-                    "Category mismatch in descriptor. Category from descriptor string: '"
-                            + tokens[currTokenIndex]
-                            + "'. Category name from category: '"
-                            + category.getName() + "'.");
-        }
-        currTokenIndex++;
-    }
-
-    private void matchStatementType() throws DescriptorParsingException {
-        if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) {
-            // QUERY
-            currTokenIndex++;
-        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_QUERY_COUNT])) {
-            // QUERY-COUNT
-            currTokenIndex++;
-        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) {
-            // ADD
-            currTokenIndex++;
-        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) {
-            // REPLACE
-            currTokenIndex++;
-        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) {
-            // UPDATE
-            currTokenIndex++;
-        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) {
-            // REMOVE
-            currTokenIndex++;
-        } else {
-            throw new DescriptorParsingException("Unknown statement type: '" + tokens[currTokenIndex] + "'");
-        }
-    }
-    
-    private static class FreeVarTypeToken {
-        
-        private final boolean isArrayType;
-        private final Class<?> componentType;
-        
-        private FreeVarTypeToken(Class<?> componentType, boolean isArrayType) {
-            this.isArrayType = isArrayType;
-            this.componentType = componentType;
-        }
-    }
-}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java	Mon Oct 07 15:03:04 2013 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java	Fri Oct 04 18:13:31 2013 +0200
@@ -44,7 +44,7 @@
  *
  * @see Node
  * @see SuffixExpression
- * @see StatementDescriptorParser
+ * @see BasicDescriptorParser
  */
 class WhereExpression implements Printable, Patchable {
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/BasicDescriptorParserTest.java	Fri Oct 04 18:13:31 2013 +0200
@@ -0,0 +1,2010 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.internal.statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.AggregateQuery;
+import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction;
+import com.redhat.thermostat.storage.core.BackingStorage;
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.CategoryAdapter;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.Replace;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.Update;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.AggregateCount;
+import com.redhat.thermostat.storage.model.Pojo;
+import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
+import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
+
+public class BasicDescriptorParserTest {
+
+    private BackingStorage storage;
+    private Query<AgentInformation> mockQuery;
+    private BasicDescriptorParser<AgentInformation> parser;
+    private Add<AgentInformation> mockAdd;
+    private Update<AgentInformation> mockUpdate;
+    private Replace<AgentInformation> mockReplace;
+    private Remove<AgentInformation> mockRemove;
+    
+    @SuppressWarnings("unchecked")
+    @Before
+    public void setup() {
+        storage = mock(BackingStorage.class);
+        mockQuery = mock(Query.class);
+        when(storage.createQuery(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockQuery);
+        // setup for ADD
+        mockAdd = mock(Add.class);
+        when(storage.createAdd(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockAdd);
+        // setup for UPDATE
+        mockUpdate = mock(Update.class);
+        when(storage.createUpdate(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockUpdate);
+        // setup for REMOVE
+        mockRemove = mock(Remove.class);
+        when(storage.createRemove(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockRemove);
+        // setup for REPLACE
+        mockReplace = mock(Replace.class);
+        when(storage.createReplace(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockReplace);
+    }
+    
+    @After
+    public void teardown() {
+        storage = null;
+        mockQuery = null;
+        mockUpdate = null;
+        mockReplace = null;
+        mockAdd = null;
+        mockRemove = null;
+    }
+    
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testParseAggregateCount() throws DescriptorParsingException {
+        Query<AggregateCount> query = mock(AggregateQuery.class);
+        ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
+        when(storage.createAggregateQuery(eq(AggregateFunction.COUNT), captor.capture())).thenReturn(query);
+        // first adapt from the target category in order to be able to produce the
+        // right aggregate query with a different result type.
+        CategoryAdapter<AgentInformation, AggregateCount> adapter = new CategoryAdapter<>(AgentInfoDAO.CATEGORY);
+        Category<AggregateCount> aggregateCategory = adapter.getAdapted(AggregateCount.class);
+        String descrString = "QUERY-COUNT " + aggregateCategory.getName() + " WHERE 'a' = 'b'";
+        StatementDescriptor<AggregateCount> desc = new StatementDescriptor<>(aggregateCategory, descrString);
+        BasicDescriptorParser<AggregateCount> parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AggregateCount> statement = (ParsedStatementImpl<AggregateCount>)parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertTrue(statement.getRawStatement() instanceof AggregateQuery);
+        Category<AggregateCount> capturedCategory = captor.getValue();
+        assertEquals(aggregateCategory, capturedCategory);
+        SuffixExpression expn = statement.getSuffixExpression();
+        assertNotNull(expn);
+        WhereExpression where = expn.getWhereExpn();
+        assertNotNull(where);
+        assertNull(expn.getSortExpn());
+        assertNull(expn.getLimitExpn());
+    }
+    
+    @Test
+    public void testParseQuerySimple() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName();
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression tree = statement.getSuffixExpression();
+        assertNull(tree.getLimitExpn());
+        assertNull(tree.getSortExpn());
+        assertNull(tree.getWhereExpn());
+    }
+    
+    @Test
+    public void testParseLongInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000000003L";
+        doTestType(descrString, 30000000003L, 0);
+    }
+    
+    @Test
+    public void testParseLongInWhere2() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000000003l";
+        doTestType(descrString, 30000000003L, 0);
+    }
+    
+    @Test
+    public void testParseLongInWhere3() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 3l";
+        doTestType(descrString, 3L, 0);
+    }
+    
+    @Test
+    public void testParseLongInWhere4() throws DescriptorParsingException {
+        long val = Long.MIN_VALUE;
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != " + val + "l";
+        doTestType(descrString, val, 0);
+    }
+    
+    @Test
+    public void testParseIntInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000";
+        doTestType(descrString, 30000, 0);
+    }
+    
+    @Test
+    public void testParseIntInWhere2() throws DescriptorParsingException {
+        int val = Integer.MAX_VALUE - 1;
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != " + val;
+        doTestType(descrString, val, 0);
+    }
+    
+    @Test
+    public void testParseIntInWhere3() throws DescriptorParsingException {
+        int val = Integer.MIN_VALUE;
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != " + val;
+        doTestType(descrString, val, 0);
+    }
+    
+    @Test
+    public void testParseBooleanInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != true";
+        doTestType(descrString, true, 0);
+    }
+    
+    @Test
+    public void testParseBooleanInWhere2() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != false";
+        doTestType(descrString, false, 0);
+    }
+    
+    @Test
+    public void testParseStringInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 'testing'";
+        doTestType(descrString, "testing", 0);
+    }
+    
+    @Test
+    public void testParseStringTypeFreeVarInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?s";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(String.class);
+        unfinished.setParameterIndex(0);
+        doTestType(descrString, unfinished, 1);
+    }
+    
+    @Test
+    public void testParseDoubleTypeFreeVarInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?d";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Double.class);
+        unfinished.setParameterIndex(0);
+        doTestType(descrString, unfinished, 1);
+    }
+    
+    /*
+     * SET clauses allow for setting list types. Test that it works for a
+     * String list free variable type in ADD.
+     */
+    @Test
+    public void testParseStringListTypeFreeVarInSetlist() throws DescriptorParsingException {
+        // use ADD since WHERE should not support list types.
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?s[";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(String.class);
+        unfinished.setArrayType(true);
+        unfinished.setParameterIndex(0);
+        doListTypeTest(descString, unfinished);
+    }
+
+    /*
+     * SET clauses allow for setting list types. Test that it works for a
+     * integer list free variable type in ADD.
+     */
+    @Test
+    public void testParseDoubleListTypeFreeVarInSetlist() throws DescriptorParsingException {
+        // use ADD since WHERE should not support list types.
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?d[";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Double.class);
+        unfinished.setArrayType(true);
+        unfinished.setParameterIndex(0);
+        doListTypeTest(descString, unfinished);
+    }
+    
+    /*
+     * SET clauses allow for setting list types. Test that it works for a
+     * double list free variable type in ADD.
+     */
+    @Test
+    public void testParseIntListTypeFreeVarInSetlist() throws DescriptorParsingException {
+        // use ADD since WHERE should not support list types.
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?i[";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Integer.class);
+        unfinished.setArrayType(true);
+        unfinished.setParameterIndex(0);
+        doListTypeTest(descString, unfinished);
+    }
+    
+    /*
+     * SET clauses allow for setting list types. Test that it works for a
+     * boolean list free variable type in ADD.
+     */
+    @Test
+    public void testParseBooleanListTypeFreeVarInSetlist() throws DescriptorParsingException {
+        // use ADD since WHERE should not support list types.
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?b[";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Boolean.class);
+        unfinished.setArrayType(true);
+        unfinished.setParameterIndex(0);
+        doListTypeTest(descString, unfinished);
+    }
+    
+    /*
+     * SET clauses allow for setting list types. Test that it works for a
+     * long list free variable type in ADD.
+     */
+    @Test
+    public void testParseLongListTypeFreeVarInSetlist() throws DescriptorParsingException {
+        // use ADD since WHERE should not support list types.
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?l[";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Long.class);
+        unfinished.setArrayType(true);
+        unfinished.setParameterIndex(0);
+        doListTypeTest(descString, unfinished);
+    }
+    
+    /*
+     * SET clauses allow for setting list types. Test that it works for a
+     * long list free variable type in ADD.
+     */
+    @Test
+    public void testParsePojoTypeFreeVarInSetlist() throws DescriptorParsingException {
+        // use ADD since WHERE should not support pojo type.
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?p";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Pojo.class);
+        unfinished.setParameterIndex(0);
+        doListTypeTest(descString, unfinished);
+    }
+    
+    /*
+     * SET clauses allow for setting list types. Test that it works for a
+     * Pojo list free variable type in ADD.
+     */
+    @Test
+    public void testParsePojoListTypeFreeVarInSetlist() throws DescriptorParsingException {
+        // use ADD since WHERE should not support list types.
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?p[";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Pojo.class);
+        unfinished.setArrayType(true);
+        unfinished.setParameterIndex(0);
+        doListTypeTest(descString, unfinished);
+    }
+
+    private void doListTypeTest(String descString, UnfinishedValueNode unfinished) {
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertTrue(statement.getRawStatement() instanceof Add);
+        SuffixExpression suffix = statement.getSuffixExpression();
+        assertNotNull(suffix);
+        assertNull(suffix.getLimitExpn());
+        assertNull(suffix.getSortExpn());
+        assertNull(suffix.getWhereExpn());
+        SetList list = statement.getSetList();
+        assertNotNull(list);
+        List<SetListValue> mems = list.getValues();
+        assertEquals(1, mems.size());
+        SetListValue val = mems.get(0);
+        TerminalNode aKey = new TerminalNode(null);
+        aKey.setValue(new Key<>("a"));
+        assertEquals(aKey, val.getKey());
+        TerminalNode aVal = new TerminalNode(null);
+        aVal.setValue(unfinished);
+        assertEquals(aVal, val.getValue());
+    }
+    
+    @Test
+    public void testParseIntTypeFreeVarInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?i";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Integer.class);
+        unfinished.setParameterIndex(0);
+        doTestType(descrString, unfinished, 1);
+    }
+    
+    @Test
+    public void testParseLongTypeFreeVarInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?l";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Long.class);
+        unfinished.setParameterIndex(0);
+        doTestType(descrString, unfinished, 1);
+    }
+    
+    @Test
+    public void testParseBooleanTypeFreeVarInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?b";
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setLHS(false);
+        unfinished.setType(Boolean.class);
+        unfinished.setParameterIndex(0);
+        doTestType(descrString, unfinished, 1);
+    }
+    
+    private void doTestType(String strDesc, Object bVal, int expNumFreeVars) throws DescriptorParsingException {
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, strDesc);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(expNumFreeVars, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression tree = statement.getSuffixExpression();
+        assertNull(tree.getLimitExpn());
+        assertNull(tree.getSortExpn());
+        assertNotNull(tree.getWhereExpn());
+        
+        WhereExpression expected = new WhereExpression();
+        BinaryExpressionNode notEquals = new BinaryExpressionNode(expected.getRoot());
+        expected.getRoot().setValue(notEquals);
+        notEquals.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
+        TerminalNode a = new TerminalNode(notEquals);
+        a.setValue(new Key<String>("a"));
+        TerminalNode b = new TerminalNode(notEquals);
+        b.setValue(bVal);
+        notEquals.setLeftChild(a);
+        notEquals.setRightChild(b);
+        
+        assertTrue(WhereExpressions.equals(expected, tree.getWhereExpn()));
+    }
+    
+    @Test
+    public void testParseNotEqualComparisonInWhere() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression tree = statement.getSuffixExpression();
+        assertNull(tree.getLimitExpn());
+        assertNull(tree.getSortExpn());
+        assertNotNull(tree.getWhereExpn());
+        
+        WhereExpression expected = new WhereExpression();
+        BinaryExpressionNode notEquals = new BinaryExpressionNode(expected.getRoot());
+        expected.getRoot().setValue(notEquals);
+        notEquals.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
+        TerminalNode a = new TerminalNode(notEquals);
+        a.setValue(new Key<String>("a"));
+        TerminalNode b = new TerminalNode(notEquals);
+        b.setValue("b");
+        notEquals.setLeftChild(a);
+        notEquals.setRightChild(b);
+        
+        assertTrue(WhereExpressions.equals(expected, tree.getWhereExpn()));
+    }
+    
+    @Test
+    public void testParseQuerySimpleWithLimit() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?i";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(1, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression expn = statement.getSuffixExpression();
+        assertNotNull(expn.getLimitExpn());
+        assertNull(expn.getSortExpn());
+        assertNull(expn.getWhereExpn());
+        LimitExpression limitExp = expn.getLimitExpn();
+        assertTrue(limitExp.getValue() instanceof UnfinishedLimitValue);
+        UnfinishedLimitValue value = (UnfinishedLimitValue)limitExp.getValue();
+        assertEquals(0, value.getParameterIndex());
+    }
+    
+    @Test
+    public void testParseSortMultiple() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT 'a' ASC , 'b' DSC , 'c' ASC";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        SortExpression sortExpn = suffixExpn.getSortExpn();
+        assertNotNull(sortExpn);
+        assertNull(suffixExpn.getLimitExpn());
+        assertNull(suffixExpn.getWhereExpn());
+        List<SortMember> list = sortExpn.getMembers();
+        assertNotNull(list);
+        assertEquals(3, list.size());
+        assertEquals(SortDirection.ASCENDING, list.get(0).getDirection());
+        assertEquals(SortDirection.DESCENDING, list.get(1).getDirection());
+        assertEquals(SortDirection.ASCENDING, list.get(2).getDirection());
+        assertEquals("a", list.get(0).getSortKey());
+        assertEquals("b", list.get(1).getSortKey());
+        assertEquals("c", list.get(2).getSortKey());
+    }
+    
+    @Test
+    public void testParseQueryWithMultipleConcunctions() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' AND 'c' = 'd' AND 'e' < ?i";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(1, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression expn = statement.getSuffixExpression();
+        assertNull(expn.getLimitExpn());
+        assertNull(expn.getSortExpn());
+        assertNotNull(expn.getWhereExpn());
+        WhereExpression where = expn.getWhereExpn();
+        // build the expected expression tree
+        WhereExpression expected = new WhereExpression();
+        BinaryExpressionNode and1 = new BinaryExpressionNode(expected.getRoot());
+        expected.getRoot().setValue(and1);
+        BinaryExpressionNode and2 = new BinaryExpressionNode(and1);
+        and1.setLeftChild(and2);
+        and1.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode equality1 = new BinaryExpressionNode(and2);
+        and2.setOperator(BinaryLogicalOperator.AND);
+        and2.setLeftChild(equality1);
+        equality1.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality1);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        equality1.setLeftChild(a);
+        TerminalNode b = new TerminalNode(equality1);
+        b.setValue("b");
+        equality1.setRightChild(b);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(and2);
+        and2.setRightChild(equality2);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality2);
+        Key<String> cKey = new Key<>("c");
+        c.setValue(cKey);
+        equality2.setLeftChild(c);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue("d");
+        equality2.setRightChild(d);
+        BinaryExpressionNode lessThan = new BinaryExpressionNode(and1);
+        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
+        and1.setRightChild(lessThan);
+        TerminalNode e = new TerminalNode(lessThan);
+        Key<Integer> eKey = new Key<>("e");
+        e.setValue(eKey);
+        lessThan.setLeftChild(e);
+        UnfinishedValueNode f = new UnfinishedValueNode();
+        f.setParameterIndex(0);
+        f.setType(Integer.class);
+        f.setLHS(false);
+        TerminalNode fReal = new TerminalNode(lessThan);
+        fReal.setValue(f);
+        lessThan.setRightChild(fReal);
+        
+        assertTrue( WhereExpressions.equals(expected, where));
+    }
+    
+    @Test
+    public void testParseQueryWithMultipleConcunctions2() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' AND 'c' = 'd' AND 'e' < 'f' AND 'g' >= 'h'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression expn = statement.getSuffixExpression();
+        assertNull(expn.getLimitExpn());
+        assertNull(expn.getSortExpn());
+        assertNotNull(expn.getWhereExpn());
+        WhereExpression where = expn.getWhereExpn();
+        // build the expected expression tree
+        WhereExpression expected = new WhereExpression();
+        BinaryExpressionNode and1 = new BinaryExpressionNode(expected.getRoot());
+        expected.getRoot().setValue(and1);
+        BinaryExpressionNode and2 = new BinaryExpressionNode(and1);
+        and1.setLeftChild(and2);
+        BinaryExpressionNode and3 = new BinaryExpressionNode(and2);
+        and3.setOperator(BinaryLogicalOperator.AND);
+        and2.setLeftChild(and3);
+        and1.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode equality1 = new BinaryExpressionNode(and3);
+        and2.setOperator(BinaryLogicalOperator.AND);
+        and3.setLeftChild(equality1);
+        equality1.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality1);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        equality1.setLeftChild(a);
+        TerminalNode b = new TerminalNode(equality1);
+        b.setValue("b");
+        equality1.setRightChild(b);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(and3);
+        and3.setRightChild(equality2);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality2);
+        Key<String> cKey = new Key<>("c");
+        c.setValue(cKey);
+        equality2.setLeftChild(c);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue("d");
+        equality2.setRightChild(d);
+        BinaryExpressionNode lessThan = new BinaryExpressionNode(and2);
+        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
+        and2.setRightChild(lessThan);
+        TerminalNode e = new TerminalNode(lessThan);
+        Key<String> eKey = new Key<>("e");
+        e.setValue(eKey);
+        lessThan.setLeftChild(e);
+        TerminalNode f = new TerminalNode(lessThan);
+        f.setValue("f");
+        lessThan.setRightChild(f);
+        BinaryExpressionNode greaterOrEqual = new BinaryExpressionNode(and1);
+        greaterOrEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
+        TerminalNode g = new TerminalNode(greaterOrEqual);
+        Key<String> gKey = new Key<>("g");
+        g.setValue(gKey);
+        greaterOrEqual.setLeftChild(g);
+        TerminalNode h = new TerminalNode(greaterOrEqual);
+        h.setValue("h");
+        greaterOrEqual.setRightChild(h);
+        and1.setRightChild(greaterOrEqual);
+        
+        assertTrue( WhereExpressions.equals(expected, where));
+    }
+    
+    @Test
+    public void testParseQueryWithMultipleDisjunctions() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < ?i";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(1, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression expn = statement.getSuffixExpression();
+        assertNull(expn.getLimitExpn());
+        assertNull(expn.getSortExpn());
+        assertNotNull(expn.getWhereExpn());
+        
+        WhereExpression where = expn.getWhereExpn();
+        // build the expected expression tree
+        WhereExpression expected = new WhereExpression();
+        BinaryExpressionNode or1 = new BinaryExpressionNode(expected.getRoot());
+        expected.getRoot().setValue(or1);
+        BinaryExpressionNode or2 = new BinaryExpressionNode(or1);
+        or1.setLeftChild(or2);
+        or1.setOperator(BinaryLogicalOperator.OR);
+        BinaryExpressionNode equality1 = new BinaryExpressionNode(or2);
+        or2.setOperator(BinaryLogicalOperator.OR);
+        or2.setLeftChild(equality1);
+        equality1.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality1);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        equality1.setLeftChild(a);
+        TerminalNode b = new TerminalNode(equality1);
+        b.setValue("b");
+        equality1.setRightChild(b);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(or2);
+        or2.setRightChild(equality2);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality2);
+        Key<String> cKey = new Key<>("c");
+        c.setValue(cKey);
+        equality2.setLeftChild(c);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue("d");
+        equality2.setRightChild(d);
+        BinaryExpressionNode lessThan = new BinaryExpressionNode(or1);
+        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
+        or1.setRightChild(lessThan);
+        TerminalNode e = new TerminalNode(lessThan);
+        Key<Integer> eKey = new Key<>("e");
+        e.setValue(eKey);
+        lessThan.setLeftChild(e);
+        UnfinishedValueNode f = new UnfinishedValueNode();
+        f.setParameterIndex(0);
+        f.setType(Integer.class);
+        f.setLHS(false);
+        TerminalNode fReal = new TerminalNode(lessThan);
+        fReal.setValue(f);
+        lessThan.setRightChild(fReal);
+        
+        assertTrue( WhereExpressions.equals(expected, where));
+    }
+    
+    @Test
+    public void testParseQueryWithMultipleDisjunctions2() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression expn = statement.getSuffixExpression();
+        assertNull(expn.getLimitExpn());
+        assertNull(expn.getSortExpn());
+        assertNotNull(expn.getWhereExpn());
+        
+        WhereExpression where = expn.getWhereExpn();
+        // build the expected expression tree
+        WhereExpression expected = new WhereExpression();
+        BinaryExpressionNode or1 = new BinaryExpressionNode(expected.getRoot());
+        expected.getRoot().setValue(or1);
+        BinaryExpressionNode or2 = new BinaryExpressionNode(or1);
+        or1.setLeftChild(or2);
+        BinaryExpressionNode or3 = new BinaryExpressionNode(or2);
+        or3.setOperator(BinaryLogicalOperator.OR);
+        or2.setLeftChild(or3);
+        or1.setOperator(BinaryLogicalOperator.OR);
+        BinaryExpressionNode equality1 = new BinaryExpressionNode(or3);
+        or2.setOperator(BinaryLogicalOperator.OR);
+        or3.setLeftChild(equality1);
+        equality1.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality1);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        equality1.setLeftChild(a);
+        TerminalNode b = new TerminalNode(equality1);
+        b.setValue("b");
+        equality1.setRightChild(b);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(or3);
+        or3.setRightChild(equality2);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality2);
+        Key<String> cKey = new Key<>("c");
+        c.setValue(cKey);
+        equality2.setLeftChild(c);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue("d");
+        equality2.setRightChild(d);
+        BinaryExpressionNode lessThan = new BinaryExpressionNode(or2);
+        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
+        or2.setRightChild(lessThan);
+        TerminalNode e = new TerminalNode(lessThan);
+        Key<String> eKey = new Key<>("e");
+        e.setValue(eKey);
+        lessThan.setLeftChild(e);
+        TerminalNode f = new TerminalNode(lessThan);
+        f.setValue("f");
+        lessThan.setRightChild(f);
+        BinaryExpressionNode greaterOrEqual = new BinaryExpressionNode(or1);
+        greaterOrEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
+        TerminalNode g = new TerminalNode(greaterOrEqual);
+        Key<String> gKey = new Key<>("g");
+        g.setValue(gKey);
+        greaterOrEqual.setLeftChild(g);
+        TerminalNode h = new TerminalNode(greaterOrEqual);
+        h.setValue("h");
+        greaterOrEqual.setRightChild(h);
+        or1.setRightChild(greaterOrEqual);
+        
+        assertTrue( WhereExpressions.equals(expected, where));
+    }
+    
+    @Test
+    public void testParseQueryWithMultipleConDisjunctions() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h' AND 'x' = 'y' AND 'u' = 'w' AND 's' = 't'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression expn = statement.getSuffixExpression();
+        assertNull(expn.getLimitExpn());
+        assertNull(expn.getSortExpn());
+        assertNotNull(expn.getWhereExpn());
+        
+        WhereExpression where = expn.getWhereExpn();
+        // build the expected expression tree
+        WhereExpression expected = new WhereExpression();
+        BinaryExpressionNode and3 = new BinaryExpressionNode(expected.getRoot());
+        expected.getRoot().setValue(and3);
+        and3.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode and2 = new BinaryExpressionNode(and3);
+        and2.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode and1 = new BinaryExpressionNode(and2);
+        and1.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode equality3 = new BinaryExpressionNode(and1);
+        equality3.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode x = new TerminalNode(equality3);
+        x.setValue(new Key<>("x"));
+        TerminalNode y = new TerminalNode(equality3);
+        y.setValue("y");
+        equality3.setLeftChild(x);
+        equality3.setRightChild(y);
+        and1.setRightChild(equality3);
+        and3.setLeftChild(and2);
+        and2.setLeftChild(and1);
+        BinaryExpressionNode equality4 = new BinaryExpressionNode(and2);
+        equality4.setOperator(BinaryComparisonOperator.EQUALS);
+        and2.setRightChild(equality4);
+        TerminalNode u = new TerminalNode(equality4);
+        u.setValue(new Key<>("u"));
+        equality4.setLeftChild(u);
+        TerminalNode w = new TerminalNode(equality4);
+        w.setValue("w");
+        equality4.setRightChild(w);
+        BinaryExpressionNode equality5 = new BinaryExpressionNode(and3);
+        equality5.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode s = new TerminalNode(equality5);
+        s.setValue(new Key<>("s"));
+        TerminalNode t = new TerminalNode(equality5);
+        t.setValue("t");
+        equality5.setLeftChild(s);
+        equality5.setRightChild(t);
+        and3.setRightChild(equality5);
+        BinaryExpressionNode or3 = new BinaryExpressionNode(and1);
+        BinaryExpressionNode or2 = new BinaryExpressionNode(or3);
+        BinaryExpressionNode or1 = new BinaryExpressionNode(or2);
+        or3.setOperator(BinaryLogicalOperator.OR);
+        or2.setOperator(BinaryLogicalOperator.OR);
+        or1.setOperator(BinaryLogicalOperator.OR);
+        or3.setLeftChild(or2);
+        or2.setLeftChild(or1);
+        BinaryExpressionNode equality1 = new BinaryExpressionNode(or1);
+        or1.setLeftChild(equality1);
+        equality1.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality1);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        equality1.setLeftChild(a);
+        TerminalNode b = new TerminalNode(equality1);
+        b.setValue("b");
+        equality1.setRightChild(b);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(or1);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        or1.setRightChild(equality2);
+        TerminalNode c = new TerminalNode(equality2);
+        Key<String> cKey = new Key<>("c");
+        c.setValue(cKey);
+        equality2.setLeftChild(c);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue("d");
+        equality2.setRightChild(d);
+        BinaryExpressionNode lessThan = new BinaryExpressionNode(or2);
+        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
+        or2.setRightChild(lessThan);
+        or2.setLeftChild(or1);
+        TerminalNode e = new TerminalNode(lessThan);
+        Key<String> eKey = new Key<>("e");
+        e.setValue(eKey);
+        lessThan.setLeftChild(e);
+        TerminalNode f = new TerminalNode(lessThan);
+        f.setValue("f");
+        lessThan.setRightChild(f);
+        BinaryExpressionNode greaterOrEqual = new BinaryExpressionNode(or3);
+        greaterOrEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
+        TerminalNode g = new TerminalNode(greaterOrEqual);
+        Key<String> gKey = new Key<>("g");
+        g.setValue(gKey);
+        greaterOrEqual.setLeftChild(g);
+        TerminalNode h = new TerminalNode(greaterOrEqual);
+        h.setValue("h");
+        greaterOrEqual.setRightChild(h);
+        or3.setRightChild(greaterOrEqual);
+        or3.setLeftChild(or2);
+        and1.setLeftChild(or3);
+        
+        assertTrue(WhereExpressions.equals(expected, where));
+    }
+    
+    @Test
+    public void testParseQueryWithMultipleConDisjunctionsNegations() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR NOT 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h' AND NOT 'x' = 'y' AND 'u' = 'w' AND 's' = 't'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression expn = statement.getSuffixExpression();
+        assertNull(expn.getLimitExpn());
+        assertNull(expn.getSortExpn());
+        assertNotNull(expn.getWhereExpn());
+        
+        WhereExpression where = expn.getWhereExpn();
+        // build the expected expression tree
+        WhereExpression expected = new WhereExpression();
+        BinaryExpressionNode and3 = new BinaryExpressionNode(expected.getRoot());
+        expected.getRoot().setValue(and3);
+        and3.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode and2 = new BinaryExpressionNode(and3);
+        and2.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode and1 = new BinaryExpressionNode(and2);
+        and1.setOperator(BinaryLogicalOperator.AND);
+        NotBooleanExpressionNode not2 = new NotBooleanExpressionNode(and1);
+        BinaryExpressionNode equality3 = new BinaryExpressionNode(not2);
+        not2.setValue(equality3);
+        equality3.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode x = new TerminalNode(equality3);
+        x.setValue(new Key<String>("x"));
+        TerminalNode y = new TerminalNode(equality3);
+        y.setValue("y");
+        equality3.setLeftChild(x);
+        equality3.setRightChild(y);
+        and1.setRightChild(not2);
+        and3.setLeftChild(and2);
+        and2.setLeftChild(and1);
+        BinaryExpressionNode equality4 = new BinaryExpressionNode(and2);
+        equality4.setOperator(BinaryComparisonOperator.EQUALS);
+        and2.setRightChild(equality4);
+        TerminalNode u = new TerminalNode(equality4);
+        u.setValue(new Key<String>("u"));
+        equality4.setLeftChild(u);
+        TerminalNode w = new TerminalNode(equality4);
+        w.setValue("w");
+        equality4.setRightChild(w);
+        BinaryExpressionNode equality5 = new BinaryExpressionNode(and3);
+        equality5.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode s = new TerminalNode(equality5);
+        s.setValue(new Key<String>("s"));
+        TerminalNode t = new TerminalNode(equality5);
+        t.setValue("t");
+        equality5.setLeftChild(s);
+        equality5.setRightChild(t);
+        and3.setRightChild(equality5);
+        BinaryExpressionNode or3 = new BinaryExpressionNode(and1);
+        BinaryExpressionNode or2 = new BinaryExpressionNode(or3);
+        BinaryExpressionNode or1 = new BinaryExpressionNode(or2);
+        or3.setOperator(BinaryLogicalOperator.OR);
+        or2.setOperator(BinaryLogicalOperator.OR);
+        or1.setOperator(BinaryLogicalOperator.OR);
+        or3.setLeftChild(or2);
+        or2.setLeftChild(or1);
+        BinaryExpressionNode equality1 = new BinaryExpressionNode(or1);
+        or1.setLeftChild(equality1);
+        equality1.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality1);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        equality1.setLeftChild(a);
+        TerminalNode b = new TerminalNode(equality1);
+        b.setValue("b");
+        equality1.setRightChild(b);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(or1);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        NotBooleanExpressionNode not1 = new NotBooleanExpressionNode(or1);
+        not1.setValue(equality2);
+        or1.setRightChild(not1);
+        TerminalNode c = new TerminalNode(equality2);
+        Key<String> cKey = new Key<>("c");
+        c.setValue(cKey);
+        equality2.setLeftChild(c);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue("d");
+        equality2.setRightChild(d);
+        BinaryExpressionNode lessThan = new BinaryExpressionNode(or2);
+        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
+        or2.setRightChild(lessThan);
+        or2.setLeftChild(or1);
+        TerminalNode e = new TerminalNode(lessThan);
+        Key<String> eKey = new Key<>("e");
+        e.setValue(eKey);
+        lessThan.setLeftChild(e);
+        TerminalNode f = new TerminalNode(lessThan);
+        f.setValue("f");
+        lessThan.setRightChild(f);
+        BinaryExpressionNode greaterOrEqual = new BinaryExpressionNode(or3);
+        greaterOrEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
+        TerminalNode g = new TerminalNode(greaterOrEqual);
+        Key<String> gKey = new Key<>("g");
+        g.setValue(gKey);
+        greaterOrEqual.setLeftChild(g);
+        TerminalNode h = new TerminalNode(greaterOrEqual);
+        h.setValue("h");
+        greaterOrEqual.setRightChild(h);
+        or3.setRightChild(greaterOrEqual);
+        or3.setLeftChild(or2);
+        and1.setLeftChild(or3);
+        
+        assertTrue(WhereExpressions.equals(expected, where));
+    }
+    
+    @Test
+    public void testParseQueryWhereAndSortMultiple() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' AND 'c' = ?s OR NOT 'x' >= ?i SORT 'a' ASC , 'b' DSC , 'c' ASC";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
+        assertEquals(2, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNotNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getLimitExpn());
+        assertNotNull(suffixExpn.getWhereExpn());
+        List<SortMember> list = suffixExpn.getSortExpn().getMembers();
+        assertNotNull(list);
+        assertEquals(3, list.size());
+        assertEquals(SortDirection.ASCENDING, list.get(0).getDirection());
+        assertEquals(SortDirection.DESCENDING, list.get(1).getDirection());
+        assertEquals(SortDirection.ASCENDING, list.get(2).getDirection());
+        assertEquals("a", list.get(0).getSortKey());
+        assertEquals("b", list.get(1).getSortKey());
+        assertEquals("c", list.get(2).getSortKey());
+        // build the expected expression tree
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode or = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(or);
+        or.setOperator(BinaryLogicalOperator.OR);
+        BinaryExpressionNode and = new BinaryExpressionNode(or);
+        and.setOperator(BinaryLogicalOperator.AND);
+        or.setLeftChild(and);
+        NotBooleanExpressionNode not = new NotBooleanExpressionNode(or);
+        or.setRightChild(not);
+        BinaryExpressionNode unequality = new BinaryExpressionNode(and);
+        unequality.setOperator(BinaryComparisonOperator.LESS_THAN);
+        TerminalNode a = new TerminalNode(unequality);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        unequality.setLeftChild(a);
+        TerminalNode b = new TerminalNode(unequality);
+        b.setValue("b");
+        unequality.setRightChild(b);
+        and.setLeftChild(unequality);
+        BinaryExpressionNode equality = new BinaryExpressionNode(and);
+        equality.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality);
+        Key<String> cKey = new Key<>("c");
+        c.setValue(cKey);
+        equality.setLeftChild(c);
+        UnfinishedValueNode patch1 = new UnfinishedValueNode();
+        patch1.setParameterIndex(0);
+        patch1.setLHS(false);
+        patch1.setType(String.class);
+        TerminalNode d = new TerminalNode(equality);
+        d.setValue(patch1);
+        equality.setRightChild(d);
+        and.setRightChild(equality);
+        BinaryExpressionNode greaterEqual = new BinaryExpressionNode(not);
+        not.setValue(greaterEqual);
+        greaterEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
+        TerminalNode x = new TerminalNode(greaterEqual);
+        Key<Integer> xKey = new Key<>("x");
+        x.setValue(xKey);
+        greaterEqual.setLeftChild(x);
+        UnfinishedValueNode patch2 = new UnfinishedValueNode();
+        patch2.setParameterIndex(1);
+        patch2.setLHS(false);
+        patch2.setType(Integer.class);
+        TerminalNode y = new TerminalNode(greaterEqual);
+        y.setValue(patch2);
+        greaterEqual.setRightChild(y);
+        // finally assert equality
+        assertTrue( WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
+    }
+    
+    @Test
+    public void testParseQueryWhereOrSortMultiple() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' OR 'c' = ?s SORT 'a' ASC , ?s DSC , 'c' ASC";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
+        assertEquals(2, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNotNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getLimitExpn());
+        assertNotNull(suffixExpn.getWhereExpn());
+        SortExpression sortExp = suffixExpn.getSortExpn();
+        List<SortMember> list = sortExp.getMembers();
+        assertNotNull(list);
+        assertEquals(3, list.size());
+        assertEquals(SortDirection.ASCENDING, list.get(0).getDirection());
+        assertEquals(SortDirection.DESCENDING, list.get(1).getDirection());
+        assertEquals(SortDirection.ASCENDING, list.get(2).getDirection());
+        assertEquals("a", list.get(0).getSortKey());
+        UnfinishedSortKey unfinished = new UnfinishedSortKey();
+        unfinished.setParameterIndex(1);
+        assertEquals(unfinished, list.get(1).getSortKey());
+        assertEquals("c", list.get(2).getSortKey());
+        // build the expected expression tree
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode or = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(or);
+        or.setOperator(BinaryLogicalOperator.OR);
+        BinaryExpressionNode unequality = new BinaryExpressionNode(or);
+        unequality.setOperator(BinaryComparisonOperator.LESS_THAN);
+        TerminalNode a = new TerminalNode(unequality);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        unequality.setLeftChild(a);
+        TerminalNode b = new TerminalNode(unequality);
+        b.setValue("b");
+        unequality.setRightChild(b);
+        or.setLeftChild(unequality);
+        BinaryExpressionNode equality = new BinaryExpressionNode(or);
+        equality.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality);
+        Key<String> cKey = new Key<>("c");
+        c.setValue(cKey);
+        equality.setLeftChild(c);
+        UnfinishedValueNode patch1 = new UnfinishedValueNode();
+        patch1.setParameterIndex(0);
+        patch1.setLHS(false);
+        patch1.setType(String.class);
+        TerminalNode d = new TerminalNode(equality);
+        d.setValue(patch1);
+        equality.setRightChild(d);
+        or.setRightChild(equality);
+        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
+    }
+    
+    @Test
+    public void testParseQuerySimpleWhereAndSimpleSort() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' SORT 'a' DSC";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNotNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getLimitExpn());
+        assertNotNull(suffixExpn.getWhereExpn());
+        SortExpression sortExp = suffixExpn.getSortExpn();
+        List<SortMember> list = sortExp.getMembers();
+        assertNotNull(list);
+        assertEquals(1, list.size());
+        assertEquals(SortDirection.DESCENDING, list.get(0).getDirection());
+        assertEquals("a", list.get(0).getSortKey());
+        // build the expected expression tree
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode unequality = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(unequality);
+        unequality.setOperator(BinaryComparisonOperator.LESS_THAN);
+        TerminalNode a = new TerminalNode(unequality);
+        @SuppressWarnings("rawtypes")
+        Key aKey = new Key("a");
+        a.setValue(aKey);
+        unequality.setLeftChild(a);
+        TerminalNode b = new TerminalNode(unequality);
+        b.setValue("b");
+        unequality.setRightChild(b);
+        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
+    }
+    
+    @Test
+    public void testParseQuerySimpleWithOneWhere() {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE '" + Key.AGENT_ID.getName() + "' = ?s";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            fail(e.getMessage());
+        }
+        assertEquals(1, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getLimitExpn());
+        assertNotNull(suffixExpn.getWhereExpn());
+        // build the expected expression tree
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode equality = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(equality);
+        equality.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality);
+        @SuppressWarnings("rawtypes")
+        Key aKey = new Key(Key.AGENT_ID.getName());
+        a.setValue(aKey);
+        equality.setLeftChild(a);
+        TerminalNode b = new TerminalNode(equality);
+        UnfinishedValueNode node = new UnfinishedValueNode();
+        node.setParameterIndex(0);
+        node.setLHS(false);
+        node.setType(String.class);
+        b.setValue(node);
+        equality.setRightChild(b);
+        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
+    }
+
+    @Test
+    public void testParseSimpleWithAndOr() {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE '" + Key.AGENT_ID.getName() + "' = ?s" +
+                            " AND ?s < ?b OR 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertEquals(3, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getLimitExpn());
+        assertNotNull(suffixExpn.getWhereExpn());
+        // build the expected expression tree
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode or = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(or);
+        or.setOperator(BinaryLogicalOperator.OR);
+        BinaryExpressionNode and = new BinaryExpressionNode(or);
+        and.setOperator(BinaryLogicalOperator.AND);
+        or.setLeftChild(and);
+        BinaryExpressionNode equality = new BinaryExpressionNode(and);
+        equality.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality);
+        Key<String> aKey = new Key<>("a");
+        a.setValue(aKey);
+        equality.setLeftChild(a);
+        TerminalNode b = new TerminalNode(equality);
+        b.setValue("b");
+        equality.setRightChild(b);
+        or.setRightChild(equality);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(and);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality2);
+        Key<String> cKey = new Key<>(Key.AGENT_ID.getName());
+        c.setValue(cKey);
+        equality2.setLeftChild(c);
+        UnfinishedValueNode patch1 = new UnfinishedValueNode();
+        patch1.setParameterIndex(0);
+        patch1.setLHS(false);
+        patch1.setType(String.class);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue(patch1);
+        equality2.setRightChild(d);
+        and.setLeftChild(equality2);
+        BinaryExpressionNode lessThan = new BinaryExpressionNode(and);
+        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
+        UnfinishedValueNode patch = new UnfinishedValueNode();
+        patch.setParameterIndex(1);
+        patch.setType(String.class);
+        patch.setLHS(true);
+        TerminalNode x = new TerminalNode(lessThan);
+        x.setValue(patch);
+        lessThan.setLeftChild(x);
+        UnfinishedValueNode patch2 = new UnfinishedValueNode();
+        patch2.setLHS(false);
+        patch2.setParameterIndex(2);
+        patch2.setType(Boolean.class);
+        TerminalNode y = new TerminalNode(lessThan);
+        y.setValue(patch2);
+        lessThan.setRightChild(y);
+        and.setRightChild(lessThan);
+        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
+    }
+
+    @Test
+    public void testParseSimpleWithAnd() {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?s AND ?s = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertEquals(2, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getLimitExpn());
+        assertNotNull(suffixExpn.getWhereExpn());
+        // build the expected expression tree
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode and = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(and);
+        and.setOperator(BinaryLogicalOperator.AND);
+        BinaryExpressionNode equality = new BinaryExpressionNode(and);
+        equality.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality);
+        @SuppressWarnings("rawtypes")
+        Key aKey = new Key("a");
+        a.setValue(aKey);
+        equality.setLeftChild(a);
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setParameterIndex(0);
+        unfinished.setLHS(false);
+        unfinished.setType(String.class);
+        TerminalNode b = new TerminalNode(equality);
+        b.setValue(unfinished);
+        equality.setRightChild(b);
+        and.setLeftChild(equality);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(and);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality2);
+        UnfinishedValueNode patch1 = new UnfinishedValueNode();
+        patch1.setParameterIndex(1);
+        patch1.setType(String.class);
+        patch1.setLHS(true);
+        c.setValue(patch1);
+        equality2.setLeftChild(c);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue("d");
+        equality2.setRightChild(d);
+        and.setRightChild(equality2);
+        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
+    }
+
+    @Test
+    public void testParseSimpleWithNotOR() {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE NOT 'a' = ?s OR ?s = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertEquals(2, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getLimitExpn());
+        assertNotNull(suffixExpn.getWhereExpn());
+        // build the expected parse tree
+        WhereExpression expn = new WhereExpression();
+        BinaryExpressionNode or = new BinaryExpressionNode(expn.getRoot());
+        expn.getRoot().setValue(or);
+        or.setOperator(BinaryLogicalOperator.OR);
+        NotBooleanExpressionNode notNode = new NotBooleanExpressionNode(or);
+        BinaryExpressionNode comparison = new BinaryExpressionNode(notNode);
+        notNode.setValue(comparison);
+        or.setLeftChild(notNode);
+        comparison.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode rightCompTerm = new TerminalNode(comparison);
+        TerminalNode leftCompTerm = new TerminalNode(comparison);
+        @SuppressWarnings("rawtypes")
+        Key aKey = new Key("a");
+        leftCompTerm.setValue(aKey);
+        UnfinishedValueNode patch1 = new UnfinishedValueNode();
+        patch1.setParameterIndex(0);
+        patch1.setType(String.class);
+        patch1.setLHS(false);
+        rightCompTerm.setValue(patch1);
+        comparison.setLeftChild(leftCompTerm);
+        comparison.setRightChild(rightCompTerm);
+        BinaryExpressionNode otherComparison = new BinaryExpressionNode(or);
+        otherComparison.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode leftUnfinished = new TerminalNode(otherComparison);
+        UnfinishedValueNode patch2 = new UnfinishedValueNode();
+        patch2.setParameterIndex(1);
+        patch2.setLHS(true);
+        patch2.setType(String.class);
+        leftUnfinished.setValue(patch2);
+        TerminalNode other = new TerminalNode(otherComparison);
+        other.setValue("d");
+        otherComparison.setLeftChild(leftUnfinished);
+        otherComparison.setRightChild(other);
+        or.setRightChild(otherComparison);
+        assertTrue(WhereExpressions.equals(expn, suffixExpn.getWhereExpn()));
+    }
+
+    @Test
+    public void testParseSimpleWithOr() {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?s OR ?s = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertEquals(2, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getLimitExpn());
+        assertNotNull(suffixExpn.getWhereExpn());
+        // build the expected parse tree
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode and = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(and);
+        and.setOperator(BinaryLogicalOperator.OR);
+        BinaryExpressionNode equality = new BinaryExpressionNode(and);
+        equality.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode a = new TerminalNode(equality);
+        @SuppressWarnings("rawtypes")
+        Key aKey = new Key("a");
+        a.setValue(aKey);
+        equality.setLeftChild(a);
+        UnfinishedValueNode unfinished = new UnfinishedValueNode();
+        unfinished.setParameterIndex(0);
+        unfinished.setType(String.class);
+        unfinished.setLHS(false);
+        TerminalNode b = new TerminalNode(equality);
+        b.setValue(unfinished);
+        equality.setRightChild(b);
+        and.setLeftChild(equality);
+        BinaryExpressionNode equality2 = new BinaryExpressionNode(and);
+        equality2.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode c = new TerminalNode(equality2);
+        UnfinishedValueNode patch1 = new UnfinishedValueNode();
+        patch1.setParameterIndex(1);
+        patch1.setType(String.class);
+        patch1.setLHS(true);
+        c.setValue(patch1);
+        equality2.setLeftChild(c);
+        TerminalNode d = new TerminalNode(equality2);
+        d.setValue("d");
+        equality2.setRightChild(d);
+        and.setRightChild(equality2);
+        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
+    }
+    
+    @Test
+    public void testParseSimpleWithLimit() {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT 1";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertEquals(0, statement.getNumParams());
+        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
+        SuffixExpression suffixExpn = statement.getSuffixExpression();
+        assertNull(suffixExpn.getSortExpn());
+        assertNull(suffixExpn.getWhereExpn());
+        assertNotNull(suffixExpn.getLimitExpn());
+        assertEquals(1, suffixExpn.getLimitExpn().getValue());
+    }
+    
+    @Test
+    public void testParseAddBasic() throws DescriptorParsingException {
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = ?s";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertTrue(statement.getRawStatement() instanceof Add);
+        SuffixExpression suffix = statement.getSuffixExpression();
+        assertNotNull(suffix);
+        WhereExpression where = suffix.getWhereExpn();
+        LimitExpression limit = suffix.getLimitExpn();
+        SortExpression sort = suffix.getSortExpn();
+        assertNull(where);
+        assertNull(limit);
+        assertNull(sort);
+        SetList setList = statement.getSetList();
+        assertNotNull(setList);
+        List<SetListValue> valuesList = setList.getValues();
+        assertEquals(2, valuesList.size());
+        SetListValue first = valuesList.get(0);
+        SetListValue second = valuesList.get(1);
+        TerminalNode a = new TerminalNode(null);
+        a.setValue(new Key<>("a"));
+        TerminalNode b = new TerminalNode(null);
+        b.setValue("b");
+        SetListValue firstExpected = new SetListValue();
+        firstExpected.setKey(a);
+        firstExpected.setValue(b);
+        assertEquals(firstExpected, first);
+        TerminalNode c = new TerminalNode(null);
+        c.setValue(new Key<>("c"));
+        UnfinishedValueNode dVal = new UnfinishedValueNode();
+        dVal.setType(String.class);
+        dVal.setLHS(false);
+        dVal.setParameterIndex(0);
+        TerminalNode d = new TerminalNode(null);
+        d.setValue(dVal);
+        SetListValue secondExpected = new SetListValue();
+        secondExpected.setKey(c);
+        secondExpected.setValue(d);
+        assertEquals(secondExpected, second);
+    }
+    
+    @Test
+    public void testParseUpdateBasic() throws DescriptorParsingException {
+        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = ?s WHERE 'foo' != ?i";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertTrue(statement.getRawStatement() instanceof Update);
+        SuffixExpression suffix = statement.getSuffixExpression();
+        assertNotNull(suffix);
+        WhereExpression where = suffix.getWhereExpn();
+        LimitExpression limit = suffix.getLimitExpn();
+        SortExpression sort = suffix.getSortExpn();
+        assertNotNull(where);
+        assertNull(limit);
+        assertNull(sort);
+        SetList setList = statement.getSetList();
+        assertNotNull(setList);
+        List<SetListValue> valuesList = setList.getValues();
+        assertEquals(2, valuesList.size());
+        SetListValue first = valuesList.get(0);
+        SetListValue second = valuesList.get(1);
+        TerminalNode a = new TerminalNode(null);
+        a.setValue(new Key<>("a"));
+        TerminalNode b = new TerminalNode(null);
+        b.setValue("b");
+        SetListValue firstExpected = new SetListValue();
+        firstExpected.setKey(a);
+        firstExpected.setValue(b);
+        assertEquals(firstExpected, first);
+        TerminalNode c = new TerminalNode(null);
+        c.setValue(new Key<>("c"));
+        UnfinishedValueNode dVal = new UnfinishedValueNode();
+        dVal.setType(String.class);
+        dVal.setLHS(false);
+        dVal.setParameterIndex(0);
+        TerminalNode d = new TerminalNode(null);
+        d.setValue(dVal);
+        SetListValue secondExpected = new SetListValue();
+        secondExpected.setKey(c);
+        secondExpected.setValue(d);
+        assertEquals(secondExpected, second);
+        // Build expected where expn
+        WhereExpression expectedWhere = new WhereExpression();
+        BinaryExpressionNode equality = new BinaryExpressionNode(expectedWhere.getRoot());
+        expectedWhere.getRoot().setValue(equality);
+        equality.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
+        TerminalNode foo = new TerminalNode(equality);
+        @SuppressWarnings("rawtypes")
+        Key fooKey = new Key("foo");
+        foo.setValue(fooKey);
+        equality.setLeftChild(foo);
+        TerminalNode fooVal = new TerminalNode(equality);
+        UnfinishedValueNode node = new UnfinishedValueNode();
+        node.setParameterIndex(1);
+        node.setLHS(false);
+        node.setType(Integer.class);
+        fooVal.setValue(node);
+        equality.setRightChild(fooVal);
+        assertTrue(WhereExpressions.equals(expectedWhere, where));
+    }
+    
+    // TODO: Add basic parse tests for REMOVE/REPLACE
+    
+    @Test
+    public void rejectLongValAsIntType() throws DescriptorParsingException {
+        // 30000000003 > Integer.MAX_VALUE; needs to be preceded by 'l/L'
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000000003";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Illegal terminal type."));
+        }
+    }
+
+    @Test
+    public void rejectLimitWhichIsNotInt() {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT illegal";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            assertEquals("Invalid limit expression. 'illegal' not an integer", e.getMessage());
+        }
+    }
+
+    @Test
+    public void rejectLHSnotString() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE a < 1";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Expected string value. Got term ->a<-"));
+        }
+    }
+    
+    /*
+     * At this point list types don't make sense in WHERE.
+     *   What should "'a' != [ 'a', 'b' ]" evaluate to?
+     *   How about this? "'key' > [ 1, 2 ]"
+     * 
+     * The only reasonable use case in WHERE would be 'a' IN [ 'a', 'b' ... ].
+     * We don't support this in a prepared context at this point. Should this
+     * change in future, this test needs to be carefully re-crafted. 
+     */
+    @Test
+    public void rejectListTypesAsFreeVarInInvalidContext() throws DescriptorParsingException {
+        List<String> illegalDescs = new ArrayList<>();
+
+        // Where should not support list types/Pojos.
+        String[] illegalWheres = new String[] {
+                " WHERE 'a' = ?s[",
+                " WHERE 'a' = ?i[",
+                " WHERE 'a' = ?p[",
+                " WHERE 'a' = ?d[",
+        };
+        
+        // Make sure we test for QUERY, QUERY-COUNT, REPLACE, UPDATE, REMOVE
+        // i.e. all that accept a WHERE.
+        String basicQuery = "QUERY " + AgentInfoDAO.CATEGORY.getName();
+        String basicQueryCount = "QUERY-COUNT " + AgentInfoDAO.CATEGORY.getName();
+        // note SET clause is legal
+        String basicUpdate = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?s[";
+        // note SET clause is legal
+        String basicReplace = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?i[";
+        String basicRemove = "REMOVE " + AgentInfoDAO.CATEGORY.getName();
+        for (String where: illegalWheres) {
+            illegalDescs.add(basicQuery + where);
+            illegalDescs.add(basicQueryCount + where);
+            illegalDescs.add(basicUpdate + where);
+            illegalDescs.add(basicReplace + where);
+            illegalDescs.add(basicRemove + where);
+        }
+        
+        // Test all illegal descs involving WHERE
+        doIllegalDescsTest(illegalDescs, "WHERE", "List");
+        
+        // Test limit too
+        illegalDescs.clear();
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?i[");
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?s[");
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?p[");
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?d[");
+        
+        doIllegalDescsTest(illegalDescs, "LIMIT", "List");
+        
+        // Test sort
+        illegalDescs.clear();
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?i[ ASC");
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?s[ ASC");
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?p[ ASC");
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?d[ ASC");
+        
+        doIllegalDescsTest(illegalDescs, "SORT", "List");
+    }
+    
+    /*
+     * Pojos don't make sense in a WHERE, LIMIT and SORT clause. This test makes
+     * sure we reject this right away.
+     * 
+     */
+    @Test
+    public void rejectPojoTypesAsFreeVarInInvalidContext() throws DescriptorParsingException {
+        List<String> illegalDescs = new ArrayList<>();
+        
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?p");
+        doIllegalDescsTest(illegalDescs, "WHERE", "Pojo");
+        illegalDescs.clear();
+        
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?p");
+        doIllegalDescsTest(illegalDescs, "LIMIT", "Pojo");
+        illegalDescs.clear();
+        
+        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?p DSC");
+        doIllegalDescsTest(illegalDescs, "SORT", "Pojo");
+    }
+    
+    private void doIllegalDescsTest(List<String> descs, String errorMsgContext, String type) {
+        for (String strDesc : descs) {
+            StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, strDesc); 
+            parser = new BasicDescriptorParser<>(storage, desc);
+            try {
+                parser.parse();
+                fail(strDesc + " should not parse");
+            } catch (DescriptorParsingException e) {
+                // pass
+                assertEquals(strDesc + " did not provide correct error message.",
+                        type + " free variable type not allowed in " + errorMsgContext + " context", e.getMessage());
+            }
+        }
+    }
+    
+    @Test
+    public void rejectIllegalFreeParamType() throws DescriptorParsingException {
+        // ? should be one of '?i', '?l', '?s', '?b', '?s['
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE ? < 1";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertEquals("Unknown type of free parameter: '?'", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void rejectParseQueryWhereAndSortMultipleIllegalSortModifier() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'somekey' < 2 AND 'c' = ?s OR 'a' >= ?i SORT 'a' ASC , 'b' ILLEGAL , 'c' ASC";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Expected ASC or DSC"));
+        }
+    }
+
+    @Test
+    public void rejectParseQueryWhereBoolTerm() throws DescriptorParsingException {
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE true AND false";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+        }
+    }
+    
+    @Test
+    public void rejectQueryWithParenthesis() throws DescriptorParsingException {
+        // We don't allow parenthesized expressions. This is due to mongodb not
+        // allowing this.
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE NOT ( 'a' = 'b' AND 'c' = 'd' )";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void rejectSimpleQueryWithMissingSpaces() throws DescriptorParsingException {
+        // we require a space before every operator/keyword
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY + " WHERE " + "'a'='b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+        }
+    }
+    
+    @Test
+    public void rejectSimpleQueryWithMissingSpaces2() throws DescriptorParsingException {
+        // we require a space before every operator/keyword
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY + " WHERE " + "'a' ='b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+        }
+    }
+    
+    @Test
+    public void rejectSimpleQueryWithInvalidComparison() throws DescriptorParsingException {
+        // <> is illegal
+        String descrString = "QUERY " + AgentInfoDAO.CATEGORY + " WHERE " + "'a' <> 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("should not parse");
+        } catch (DescriptorParsingException e) {
+            // pass
+        }
+    }
+    
+    @Test
+    public void rejectInvalidDescriptorStringBadWhere() throws DescriptorParsingException {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " where '" + Key.AGENT_ID.getName() + "'= ?s";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("lower case where not allowed in descriptor. Should have rejected.");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Unexpected token: 'where'"));
+        }
+    }
+    
+    @Test
+    public void rejectInvalidDescriptorStringBadStatementType() throws DescriptorParsingException {
+        String descString = "UNKNOWN some-unknown-category WHERE 1 = ?i";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("UNKNOWN not a valid statement type");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Unknown statement type"));
+        }
+    }
+    
+    @Test
+    public void rejectInvalidDescriptorStringCategoryMismatch() throws DescriptorParsingException {
+        String descString = "QUERY some-unknown-category WHERE 1 = ?i";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("category names in descriptor and Category object did not match!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Category mismatch"));
+        }
+    }
+    
+    @Test
+    public void rejectInvalidDescriptorStringBadSortNoArg() throws DescriptorParsingException {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?i SORT";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("category names in descriptor and Category object did not match!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Invalid where clause"));
+        }
+    }
+    
+    @Test
+    public void rejectInvalidDescriptorStringBadWhereNoArg() throws DescriptorParsingException {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE SORT";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("category names in descriptor and Category object did not match!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("SORT"));
+            assertTrue(e.getMessage().contains("Expected string value"));
+        }
+    }
+    
+    @Test
+    public void rejectAddWithInvalidSetList() throws DescriptorParsingException {
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = , 'c' = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("Invalid SET values list.");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertEquals("Illegal terminal type. Token was ->,<-", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void rejectAddWithWhere() throws DescriptorParsingException {
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd' WHERE 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("ADD operation does not support WHERE clauses!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("WHERE clause not allowed for ADD"));
+        }
+    }
+    
+    @Test
+    public void rejectReplaceWithoutWhere() throws DescriptorParsingException {
+        String descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("REPLACE operation requires WHERE clause, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("WHERE clause required for REPLACE"));
+        }
+    }
+    
+    @Test
+    public void rejectUpdateWithoutWhere() throws DescriptorParsingException {
+        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("UPDATE operation requires WHERE clause, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("WHERE clause required for UPDATE"));
+        }
+    }
+    
+    @Test
+    public void rejectRemoveWithSetList() throws DescriptorParsingException {
+        String descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("REMOVE does not allow SET list, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("SET not allowed for REMOVE"));
+        }
+    }
+    
+    @Test
+    public void rejectQueryWithSetList() throws DescriptorParsingException {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("QUERY does not allow SET list, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("SET not allowed for QUERY"));
+        }
+    }
+    
+    
+    private void doRejectWriteSortLimitTest(String descString, String failmsg) {
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail(failmsg);
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertEquals("LIMIT/SORT only allowed for QUERY/QUERY-COUNT", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void rejectWritesWithSortLimit() throws DescriptorParsingException {
+        // Update rejects
+        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' SORT 'a' DSC";
+        String failmsg = "SORT in UPDATE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' LIMIT 1";
+        failmsg = "LIMIT in UPDATE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        
+        // Remove rejects
+        descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' LIMIT 1";
+        failmsg = "LIMIT in REMOVE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' SORT 'a' ASC";
+        failmsg = "SORT in REMOVE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        
+        // Replace rejects
+        descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' LIMIT 1";
+        failmsg = "LIMIT in REPLACE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' SORT 'a' ASC";
+        failmsg = "SORT in REPLACE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        
+        // Add rejects
+        descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' LIMIT 1";
+        failmsg = "LIMIT in ADD is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' SORT 'a' ASC";
+        failmsg = "SORT in ADD is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+    }
+    
+    @Test
+    public void rejectUpdateWithoutSet() throws DescriptorParsingException {
+        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new BasicDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("UPDATE requires SET list, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertEquals("SET list required for UPDATE", e.getMessage());
+        }
+    }
+    
+    // TODO: add tests where set list does not match all props of Pojo class for
+    // add/replace
+    
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Mon Oct 07 15:03:04 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2010 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.storage.internal.statement;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-
-import com.redhat.thermostat.storage.core.Add;
-import com.redhat.thermostat.storage.core.AggregateQuery;
-import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction;
-import com.redhat.thermostat.storage.core.BackingStorage;
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.CategoryAdapter;
-import com.redhat.thermostat.storage.core.DescriptorParsingException;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Query.SortDirection;
-import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.Replace;
-import com.redhat.thermostat.storage.core.StatementDescriptor;
-import com.redhat.thermostat.storage.core.Update;
-import com.redhat.thermostat.storage.dao.AgentInfoDAO;
-import com.redhat.thermostat.storage.model.AgentInformation;
-import com.redhat.thermostat.storage.model.AggregateCount;
-import com.redhat.thermostat.storage.model.Pojo;
-import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
-import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
-
-public class StatementDescriptorParserTest {
-
-    private BackingStorage storage;
-    private Query<AgentInformation> mockQuery;
-    private StatementDescriptorParser<AgentInformation> parser;
-    private Add<AgentInformation> mockAdd;
-    private Update<AgentInformation> mockUpdate;
-    private Replace<AgentInformation> mockReplace;
-    private Remove<AgentInformation> mockRemove;
-    
-    @SuppressWarnings("unchecked")
-    @Before
-    public void setup() {
-        storage = mock(BackingStorage.class);
-        mockQuery = mock(Query.class);
-        when(storage.createQuery(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockQuery);
-        // setup for ADD
-        mockAdd = mock(Add.class);
-        when(storage.createAdd(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockAdd);
-        // setup for UPDATE
-        mockUpdate = mock(Update.class);
-        when(storage.createUpdate(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockUpdate);
-        // setup for REMOVE
-        mockRemove = mock(Remove.class);
-        when(storage.createRemove(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockRemove);
-        // setup for REPLACE
-        mockReplace = mock(Replace.class);
-        when(storage.createReplace(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockReplace);
-    }
-    
-    @After
-    public void teardown() {
-        storage = null;
-        mockQuery = null;
-        mockUpdate = null;
-        mockReplace = null;
-        mockAdd = null;
-        mockRemove = null;
-    }
-    
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Test
-    public void testParseAggregateCount() throws DescriptorParsingException {
-        Query<AggregateCount> query = mock(AggregateQuery.class);
-        ArgumentCaptor<Category> captor = ArgumentCaptor.forClass(Category.class);
-        when(storage.createAggregateQuery(eq(AggregateFunction.COUNT), captor.capture())).thenReturn(query);
-        // first adapt from the target category in order to be able to produce the
-        // right aggregate query with a different result type.
-        CategoryAdapter<AgentInformation, AggregateCount> adapter = new CategoryAdapter<>(AgentInfoDAO.CATEGORY);
-        Category<AggregateCount> aggregateCategory = adapter.getAdapted(AggregateCount.class);
-        String descrString = "QUERY-COUNT " + aggregateCategory.getName() + " WHERE 'a' = 'b'";
-        StatementDescriptor<AggregateCount> desc = new StatementDescriptor<>(aggregateCategory, descrString);
-        StatementDescriptorParser<AggregateCount> parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AggregateCount> statement = (ParsedStatementImpl<AggregateCount>)parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertTrue(statement.getRawStatement() instanceof AggregateQuery);
-        Category<AggregateCount> capturedCategory = captor.getValue();
-        assertEquals(aggregateCategory, capturedCategory);
-        SuffixExpression expn = statement.getSuffixExpression();
-        assertNotNull(expn);
-        WhereExpression where = expn.getWhereExpn();
-        assertNotNull(where);
-        assertNull(expn.getSortExpn());
-        assertNull(expn.getLimitExpn());
-    }
-    
-    @Test
-    public void testParseQuerySimple() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName();
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression tree = statement.getSuffixExpression();
-        assertNull(tree.getLimitExpn());
-        assertNull(tree.getSortExpn());
-        assertNull(tree.getWhereExpn());
-    }
-    
-    @Test
-    public void testParseLongInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000000003L";
-        doTestType(descrString, 30000000003L, 0);
-    }
-    
-    @Test
-    public void testParseLongInWhere2() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000000003l";
-        doTestType(descrString, 30000000003L, 0);
-    }
-    
-    @Test
-    public void testParseLongInWhere3() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 3l";
-        doTestType(descrString, 3L, 0);
-    }
-    
-    @Test
-    public void testParseLongInWhere4() throws DescriptorParsingException {
-        long val = Long.MIN_VALUE;
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != " + val + "l";
-        doTestType(descrString, val, 0);
-    }
-    
-    @Test
-    public void testParseIntInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000";
-        doTestType(descrString, 30000, 0);
-    }
-    
-    @Test
-    public void testParseIntInWhere2() throws DescriptorParsingException {
-        int val = Integer.MAX_VALUE - 1;
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != " + val;
-        doTestType(descrString, val, 0);
-    }
-    
-    @Test
-    public void testParseIntInWhere3() throws DescriptorParsingException {
-        int val = Integer.MIN_VALUE;
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != " + val;
-        doTestType(descrString, val, 0);
-    }
-    
-    @Test
-    public void testParseBooleanInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != true";
-        doTestType(descrString, true, 0);
-    }
-    
-    @Test
-    public void testParseBooleanInWhere2() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != false";
-        doTestType(descrString, false, 0);
-    }
-    
-    @Test
-    public void testParseStringInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 'testing'";
-        doTestType(descrString, "testing", 0);
-    }
-    
-    @Test
-    public void testParseStringTypeFreeVarInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?s";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(String.class);
-        unfinished.setParameterIndex(0);
-        doTestType(descrString, unfinished, 1);
-    }
-    
-    @Test
-    public void testParseDoubleTypeFreeVarInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?d";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Double.class);
-        unfinished.setParameterIndex(0);
-        doTestType(descrString, unfinished, 1);
-    }
-    
-    /*
-     * SET clauses allow for setting list types. Test that it works for a
-     * String list free variable type in ADD.
-     */
-    @Test
-    public void testParseStringListTypeFreeVarInSetlist() throws DescriptorParsingException {
-        // use ADD since WHERE should not support list types.
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?s[";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(String.class);
-        unfinished.setArrayType(true);
-        unfinished.setParameterIndex(0);
-        doListTypeTest(descString, unfinished);
-    }
-
-    /*
-     * SET clauses allow for setting list types. Test that it works for a
-     * integer list free variable type in ADD.
-     */
-    @Test
-    public void testParseDoubleListTypeFreeVarInSetlist() throws DescriptorParsingException {
-        // use ADD since WHERE should not support list types.
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?d[";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Double.class);
-        unfinished.setArrayType(true);
-        unfinished.setParameterIndex(0);
-        doListTypeTest(descString, unfinished);
-    }
-    
-    /*
-     * SET clauses allow for setting list types. Test that it works for a
-     * double list free variable type in ADD.
-     */
-    @Test
-    public void testParseIntListTypeFreeVarInSetlist() throws DescriptorParsingException {
-        // use ADD since WHERE should not support list types.
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?i[";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Integer.class);
-        unfinished.setArrayType(true);
-        unfinished.setParameterIndex(0);
-        doListTypeTest(descString, unfinished);
-    }
-    
-    /*
-     * SET clauses allow for setting list types. Test that it works for a
-     * boolean list free variable type in ADD.
-     */
-    @Test
-    public void testParseBooleanListTypeFreeVarInSetlist() throws DescriptorParsingException {
-        // use ADD since WHERE should not support list types.
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?b[";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Boolean.class);
-        unfinished.setArrayType(true);
-        unfinished.setParameterIndex(0);
-        doListTypeTest(descString, unfinished);
-    }
-    
-    /*
-     * SET clauses allow for setting list types. Test that it works for a
-     * long list free variable type in ADD.
-     */
-    @Test
-    public void testParseLongListTypeFreeVarInSetlist() throws DescriptorParsingException {
-        // use ADD since WHERE should not support list types.
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?l[";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Long.class);
-        unfinished.setArrayType(true);
-        unfinished.setParameterIndex(0);
-        doListTypeTest(descString, unfinished);
-    }
-    
-    /*
-     * SET clauses allow for setting list types. Test that it works for a
-     * long list free variable type in ADD.
-     */
-    @Test
-    public void testParsePojoTypeFreeVarInSetlist() throws DescriptorParsingException {
-        // use ADD since WHERE should not support pojo type.
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?p";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Pojo.class);
-        unfinished.setParameterIndex(0);
-        doListTypeTest(descString, unfinished);
-    }
-    
-    /*
-     * SET clauses allow for setting list types. Test that it works for a
-     * Pojo list free variable type in ADD.
-     */
-    @Test
-    public void testParsePojoListTypeFreeVarInSetlist() throws DescriptorParsingException {
-        // use ADD since WHERE should not support list types.
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?p[";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Pojo.class);
-        unfinished.setArrayType(true);
-        unfinished.setParameterIndex(0);
-        doListTypeTest(descString, unfinished);
-    }
-
-    private void doListTypeTest(String descString, UnfinishedValueNode unfinished) {
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-        assertTrue(statement.getRawStatement() instanceof Add);
-        SuffixExpression suffix = statement.getSuffixExpression();
-        assertNotNull(suffix);
-        assertNull(suffix.getLimitExpn());
-        assertNull(suffix.getSortExpn());
-        assertNull(suffix.getWhereExpn());
-        SetList list = statement.getSetList();
-        assertNotNull(list);
-        List<SetListValue> mems = list.getValues();
-        assertEquals(1, mems.size());
-        SetListValue val = mems.get(0);
-        TerminalNode aKey = new TerminalNode(null);
-        aKey.setValue(new Key<>("a"));
-        assertEquals(aKey, val.getKey());
-        TerminalNode aVal = new TerminalNode(null);
-        aVal.setValue(unfinished);
-        assertEquals(aVal, val.getValue());
-    }
-    
-    @Test
-    public void testParseIntTypeFreeVarInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?i";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Integer.class);
-        unfinished.setParameterIndex(0);
-        doTestType(descrString, unfinished, 1);
-    }
-    
-    @Test
-    public void testParseLongTypeFreeVarInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?l";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Long.class);
-        unfinished.setParameterIndex(0);
-        doTestType(descrString, unfinished, 1);
-    }
-    
-    @Test
-    public void testParseBooleanTypeFreeVarInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != ?b";
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setLHS(false);
-        unfinished.setType(Boolean.class);
-        unfinished.setParameterIndex(0);
-        doTestType(descrString, unfinished, 1);
-    }
-    
-    private void doTestType(String strDesc, Object bVal, int expNumFreeVars) throws DescriptorParsingException {
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, strDesc);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(expNumFreeVars, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression tree = statement.getSuffixExpression();
-        assertNull(tree.getLimitExpn());
-        assertNull(tree.getSortExpn());
-        assertNotNull(tree.getWhereExpn());
-        
-        WhereExpression expected = new WhereExpression();
-        BinaryExpressionNode notEquals = new BinaryExpressionNode(expected.getRoot());
-        expected.getRoot().setValue(notEquals);
-        notEquals.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
-        TerminalNode a = new TerminalNode(notEquals);
-        a.setValue(new Key<String>("a"));
-        TerminalNode b = new TerminalNode(notEquals);
-        b.setValue(bVal);
-        notEquals.setLeftChild(a);
-        notEquals.setRightChild(b);
-        
-        assertTrue(WhereExpressions.equals(expected, tree.getWhereExpn()));
-    }
-    
-    @Test
-    public void testParseNotEqualComparisonInWhere() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 'b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression tree = statement.getSuffixExpression();
-        assertNull(tree.getLimitExpn());
-        assertNull(tree.getSortExpn());
-        assertNotNull(tree.getWhereExpn());
-        
-        WhereExpression expected = new WhereExpression();
-        BinaryExpressionNode notEquals = new BinaryExpressionNode(expected.getRoot());
-        expected.getRoot().setValue(notEquals);
-        notEquals.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
-        TerminalNode a = new TerminalNode(notEquals);
-        a.setValue(new Key<String>("a"));
-        TerminalNode b = new TerminalNode(notEquals);
-        b.setValue("b");
-        notEquals.setLeftChild(a);
-        notEquals.setRightChild(b);
-        
-        assertTrue(WhereExpressions.equals(expected, tree.getWhereExpn()));
-    }
-    
-    @Test
-    public void testParseQuerySimpleWithLimit() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?i";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(1, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression expn = statement.getSuffixExpression();
-        assertNotNull(expn.getLimitExpn());
-        assertNull(expn.getSortExpn());
-        assertNull(expn.getWhereExpn());
-        LimitExpression limitExp = expn.getLimitExpn();
-        assertTrue(limitExp.getValue() instanceof UnfinishedLimitValue);
-        UnfinishedLimitValue value = (UnfinishedLimitValue)limitExp.getValue();
-        assertEquals(0, value.getParameterIndex());
-    }
-    
-    @Test
-    public void testParseSortMultiple() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT 'a' ASC , 'b' DSC , 'c' ASC";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        SortExpression sortExpn = suffixExpn.getSortExpn();
-        assertNotNull(sortExpn);
-        assertNull(suffixExpn.getLimitExpn());
-        assertNull(suffixExpn.getWhereExpn());
-        List<SortMember> list = sortExpn.getMembers();
-        assertNotNull(list);
-        assertEquals(3, list.size());
-        assertEquals(SortDirection.ASCENDING, list.get(0).getDirection());
-        assertEquals(SortDirection.DESCENDING, list.get(1).getDirection());
-        assertEquals(SortDirection.ASCENDING, list.get(2).getDirection());
-        assertEquals("a", list.get(0).getSortKey());
-        assertEquals("b", list.get(1).getSortKey());
-        assertEquals("c", list.get(2).getSortKey());
-    }
-    
-    @Test
-    public void testParseQueryWithMultipleConcunctions() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' AND 'c' = 'd' AND 'e' < ?i";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(1, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression expn = statement.getSuffixExpression();
-        assertNull(expn.getLimitExpn());
-        assertNull(expn.getSortExpn());
-        assertNotNull(expn.getWhereExpn());
-        WhereExpression where = expn.getWhereExpn();
-        // build the expected expression tree
-        WhereExpression expected = new WhereExpression();
-        BinaryExpressionNode and1 = new BinaryExpressionNode(expected.getRoot());
-        expected.getRoot().setValue(and1);
-        BinaryExpressionNode and2 = new BinaryExpressionNode(and1);
-        and1.setLeftChild(and2);
-        and1.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode equality1 = new BinaryExpressionNode(and2);
-        and2.setOperator(BinaryLogicalOperator.AND);
-        and2.setLeftChild(equality1);
-        equality1.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality1);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        equality1.setLeftChild(a);
-        TerminalNode b = new TerminalNode(equality1);
-        b.setValue("b");
-        equality1.setRightChild(b);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(and2);
-        and2.setRightChild(equality2);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality2);
-        Key<String> cKey = new Key<>("c");
-        c.setValue(cKey);
-        equality2.setLeftChild(c);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue("d");
-        equality2.setRightChild(d);
-        BinaryExpressionNode lessThan = new BinaryExpressionNode(and1);
-        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
-        and1.setRightChild(lessThan);
-        TerminalNode e = new TerminalNode(lessThan);
-        Key<Integer> eKey = new Key<>("e");
-        e.setValue(eKey);
-        lessThan.setLeftChild(e);
-        UnfinishedValueNode f = new UnfinishedValueNode();
-        f.setParameterIndex(0);
-        f.setType(Integer.class);
-        f.setLHS(false);
-        TerminalNode fReal = new TerminalNode(lessThan);
-        fReal.setValue(f);
-        lessThan.setRightChild(fReal);
-        
-        assertTrue( WhereExpressions.equals(expected, where));
-    }
-    
-    @Test
-    public void testParseQueryWithMultipleConcunctions2() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' AND 'c' = 'd' AND 'e' < 'f' AND 'g' >= 'h'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression expn = statement.getSuffixExpression();
-        assertNull(expn.getLimitExpn());
-        assertNull(expn.getSortExpn());
-        assertNotNull(expn.getWhereExpn());
-        WhereExpression where = expn.getWhereExpn();
-        // build the expected expression tree
-        WhereExpression expected = new WhereExpression();
-        BinaryExpressionNode and1 = new BinaryExpressionNode(expected.getRoot());
-        expected.getRoot().setValue(and1);
-        BinaryExpressionNode and2 = new BinaryExpressionNode(and1);
-        and1.setLeftChild(and2);
-        BinaryExpressionNode and3 = new BinaryExpressionNode(and2);
-        and3.setOperator(BinaryLogicalOperator.AND);
-        and2.setLeftChild(and3);
-        and1.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode equality1 = new BinaryExpressionNode(and3);
-        and2.setOperator(BinaryLogicalOperator.AND);
-        and3.setLeftChild(equality1);
-        equality1.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality1);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        equality1.setLeftChild(a);
-        TerminalNode b = new TerminalNode(equality1);
-        b.setValue("b");
-        equality1.setRightChild(b);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(and3);
-        and3.setRightChild(equality2);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality2);
-        Key<String> cKey = new Key<>("c");
-        c.setValue(cKey);
-        equality2.setLeftChild(c);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue("d");
-        equality2.setRightChild(d);
-        BinaryExpressionNode lessThan = new BinaryExpressionNode(and2);
-        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
-        and2.setRightChild(lessThan);
-        TerminalNode e = new TerminalNode(lessThan);
-        Key<String> eKey = new Key<>("e");
-        e.setValue(eKey);
-        lessThan.setLeftChild(e);
-        TerminalNode f = new TerminalNode(lessThan);
-        f.setValue("f");
-        lessThan.setRightChild(f);
-        BinaryExpressionNode greaterOrEqual = new BinaryExpressionNode(and1);
-        greaterOrEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
-        TerminalNode g = new TerminalNode(greaterOrEqual);
-        Key<String> gKey = new Key<>("g");
-        g.setValue(gKey);
-        greaterOrEqual.setLeftChild(g);
-        TerminalNode h = new TerminalNode(greaterOrEqual);
-        h.setValue("h");
-        greaterOrEqual.setRightChild(h);
-        and1.setRightChild(greaterOrEqual);
-        
-        assertTrue( WhereExpressions.equals(expected, where));
-    }
-    
-    @Test
-    public void testParseQueryWithMultipleDisjunctions() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < ?i";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(1, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression expn = statement.getSuffixExpression();
-        assertNull(expn.getLimitExpn());
-        assertNull(expn.getSortExpn());
-        assertNotNull(expn.getWhereExpn());
-        
-        WhereExpression where = expn.getWhereExpn();
-        // build the expected expression tree
-        WhereExpression expected = new WhereExpression();
-        BinaryExpressionNode or1 = new BinaryExpressionNode(expected.getRoot());
-        expected.getRoot().setValue(or1);
-        BinaryExpressionNode or2 = new BinaryExpressionNode(or1);
-        or1.setLeftChild(or2);
-        or1.setOperator(BinaryLogicalOperator.OR);
-        BinaryExpressionNode equality1 = new BinaryExpressionNode(or2);
-        or2.setOperator(BinaryLogicalOperator.OR);
-        or2.setLeftChild(equality1);
-        equality1.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality1);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        equality1.setLeftChild(a);
-        TerminalNode b = new TerminalNode(equality1);
-        b.setValue("b");
-        equality1.setRightChild(b);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(or2);
-        or2.setRightChild(equality2);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality2);
-        Key<String> cKey = new Key<>("c");
-        c.setValue(cKey);
-        equality2.setLeftChild(c);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue("d");
-        equality2.setRightChild(d);
-        BinaryExpressionNode lessThan = new BinaryExpressionNode(or1);
-        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
-        or1.setRightChild(lessThan);
-        TerminalNode e = new TerminalNode(lessThan);
-        Key<Integer> eKey = new Key<>("e");
-        e.setValue(eKey);
-        lessThan.setLeftChild(e);
-        UnfinishedValueNode f = new UnfinishedValueNode();
-        f.setParameterIndex(0);
-        f.setType(Integer.class);
-        f.setLHS(false);
-        TerminalNode fReal = new TerminalNode(lessThan);
-        fReal.setValue(f);
-        lessThan.setRightChild(fReal);
-        
-        assertTrue( WhereExpressions.equals(expected, where));
-    }
-    
-    @Test
-    public void testParseQueryWithMultipleDisjunctions2() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression expn = statement.getSuffixExpression();
-        assertNull(expn.getLimitExpn());
-        assertNull(expn.getSortExpn());
-        assertNotNull(expn.getWhereExpn());
-        
-        WhereExpression where = expn.getWhereExpn();
-        // build the expected expression tree
-        WhereExpression expected = new WhereExpression();
-        BinaryExpressionNode or1 = new BinaryExpressionNode(expected.getRoot());
-        expected.getRoot().setValue(or1);
-        BinaryExpressionNode or2 = new BinaryExpressionNode(or1);
-        or1.setLeftChild(or2);
-        BinaryExpressionNode or3 = new BinaryExpressionNode(or2);
-        or3.setOperator(BinaryLogicalOperator.OR);
-        or2.setLeftChild(or3);
-        or1.setOperator(BinaryLogicalOperator.OR);
-        BinaryExpressionNode equality1 = new BinaryExpressionNode(or3);
-        or2.setOperator(BinaryLogicalOperator.OR);
-        or3.setLeftChild(equality1);
-        equality1.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality1);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        equality1.setLeftChild(a);
-        TerminalNode b = new TerminalNode(equality1);
-        b.setValue("b");
-        equality1.setRightChild(b);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(or3);
-        or3.setRightChild(equality2);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality2);
-        Key<String> cKey = new Key<>("c");
-        c.setValue(cKey);
-        equality2.setLeftChild(c);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue("d");
-        equality2.setRightChild(d);
-        BinaryExpressionNode lessThan = new BinaryExpressionNode(or2);
-        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
-        or2.setRightChild(lessThan);
-        TerminalNode e = new TerminalNode(lessThan);
-        Key<String> eKey = new Key<>("e");
-        e.setValue(eKey);
-        lessThan.setLeftChild(e);
-        TerminalNode f = new TerminalNode(lessThan);
-        f.setValue("f");
-        lessThan.setRightChild(f);
-        BinaryExpressionNode greaterOrEqual = new BinaryExpressionNode(or1);
-        greaterOrEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
-        TerminalNode g = new TerminalNode(greaterOrEqual);
-        Key<String> gKey = new Key<>("g");
-        g.setValue(gKey);
-        greaterOrEqual.setLeftChild(g);
-        TerminalNode h = new TerminalNode(greaterOrEqual);
-        h.setValue("h");
-        greaterOrEqual.setRightChild(h);
-        or1.setRightChild(greaterOrEqual);
-        
-        assertTrue( WhereExpressions.equals(expected, where));
-    }
-    
-    @Test
-    public void testParseQueryWithMultipleConDisjunctions() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h' AND 'x' = 'y' AND 'u' = 'w' AND 's' = 't'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression expn = statement.getSuffixExpression();
-        assertNull(expn.getLimitExpn());
-        assertNull(expn.getSortExpn());
-        assertNotNull(expn.getWhereExpn());
-        
-        WhereExpression where = expn.getWhereExpn();
-        // build the expected expression tree
-        WhereExpression expected = new WhereExpression();
-        BinaryExpressionNode and3 = new BinaryExpressionNode(expected.getRoot());
-        expected.getRoot().setValue(and3);
-        and3.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode and2 = new BinaryExpressionNode(and3);
-        and2.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode and1 = new BinaryExpressionNode(and2);
-        and1.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode equality3 = new BinaryExpressionNode(and1);
-        equality3.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode x = new TerminalNode(equality3);
-        x.setValue(new Key<>("x"));
-        TerminalNode y = new TerminalNode(equality3);
-        y.setValue("y");
-        equality3.setLeftChild(x);
-        equality3.setRightChild(y);
-        and1.setRightChild(equality3);
-        and3.setLeftChild(and2);
-        and2.setLeftChild(and1);
-        BinaryExpressionNode equality4 = new BinaryExpressionNode(and2);
-        equality4.setOperator(BinaryComparisonOperator.EQUALS);
-        and2.setRightChild(equality4);
-        TerminalNode u = new TerminalNode(equality4);
-        u.setValue(new Key<>("u"));
-        equality4.setLeftChild(u);
-        TerminalNode w = new TerminalNode(equality4);
-        w.setValue("w");
-        equality4.setRightChild(w);
-        BinaryExpressionNode equality5 = new BinaryExpressionNode(and3);
-        equality5.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode s = new TerminalNode(equality5);
-        s.setValue(new Key<>("s"));
-        TerminalNode t = new TerminalNode(equality5);
-        t.setValue("t");
-        equality5.setLeftChild(s);
-        equality5.setRightChild(t);
-        and3.setRightChild(equality5);
-        BinaryExpressionNode or3 = new BinaryExpressionNode(and1);
-        BinaryExpressionNode or2 = new BinaryExpressionNode(or3);
-        BinaryExpressionNode or1 = new BinaryExpressionNode(or2);
-        or3.setOperator(BinaryLogicalOperator.OR);
-        or2.setOperator(BinaryLogicalOperator.OR);
-        or1.setOperator(BinaryLogicalOperator.OR);
-        or3.setLeftChild(or2);
-        or2.setLeftChild(or1);
-        BinaryExpressionNode equality1 = new BinaryExpressionNode(or1);
-        or1.setLeftChild(equality1);
-        equality1.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality1);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        equality1.setLeftChild(a);
-        TerminalNode b = new TerminalNode(equality1);
-        b.setValue("b");
-        equality1.setRightChild(b);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(or1);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        or1.setRightChild(equality2);
-        TerminalNode c = new TerminalNode(equality2);
-        Key<String> cKey = new Key<>("c");
-        c.setValue(cKey);
-        equality2.setLeftChild(c);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue("d");
-        equality2.setRightChild(d);
-        BinaryExpressionNode lessThan = new BinaryExpressionNode(or2);
-        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
-        or2.setRightChild(lessThan);
-        or2.setLeftChild(or1);
-        TerminalNode e = new TerminalNode(lessThan);
-        Key<String> eKey = new Key<>("e");
-        e.setValue(eKey);
-        lessThan.setLeftChild(e);
-        TerminalNode f = new TerminalNode(lessThan);
-        f.setValue("f");
-        lessThan.setRightChild(f);
-        BinaryExpressionNode greaterOrEqual = new BinaryExpressionNode(or3);
-        greaterOrEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
-        TerminalNode g = new TerminalNode(greaterOrEqual);
-        Key<String> gKey = new Key<>("g");
-        g.setValue(gKey);
-        greaterOrEqual.setLeftChild(g);
-        TerminalNode h = new TerminalNode(greaterOrEqual);
-        h.setValue("h");
-        greaterOrEqual.setRightChild(h);
-        or3.setRightChild(greaterOrEqual);
-        or3.setLeftChild(or2);
-        and1.setLeftChild(or3);
-        
-        assertTrue(WhereExpressions.equals(expected, where));
-    }
-    
-    @Test
-    public void testParseQueryWithMultipleConDisjunctionsNegations() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' OR NOT 'c' = 'd' OR 'e' < 'f' OR 'g' >= 'h' AND NOT 'x' = 'y' AND 'u' = 'w' AND 's' = 't'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression expn = statement.getSuffixExpression();
-        assertNull(expn.getLimitExpn());
-        assertNull(expn.getSortExpn());
-        assertNotNull(expn.getWhereExpn());
-        
-        WhereExpression where = expn.getWhereExpn();
-        // build the expected expression tree
-        WhereExpression expected = new WhereExpression();
-        BinaryExpressionNode and3 = new BinaryExpressionNode(expected.getRoot());
-        expected.getRoot().setValue(and3);
-        and3.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode and2 = new BinaryExpressionNode(and3);
-        and2.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode and1 = new BinaryExpressionNode(and2);
-        and1.setOperator(BinaryLogicalOperator.AND);
-        NotBooleanExpressionNode not2 = new NotBooleanExpressionNode(and1);
-        BinaryExpressionNode equality3 = new BinaryExpressionNode(not2);
-        not2.setValue(equality3);
-        equality3.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode x = new TerminalNode(equality3);
-        x.setValue(new Key<String>("x"));
-        TerminalNode y = new TerminalNode(equality3);
-        y.setValue("y");
-        equality3.setLeftChild(x);
-        equality3.setRightChild(y);
-        and1.setRightChild(not2);
-        and3.setLeftChild(and2);
-        and2.setLeftChild(and1);
-        BinaryExpressionNode equality4 = new BinaryExpressionNode(and2);
-        equality4.setOperator(BinaryComparisonOperator.EQUALS);
-        and2.setRightChild(equality4);
-        TerminalNode u = new TerminalNode(equality4);
-        u.setValue(new Key<String>("u"));
-        equality4.setLeftChild(u);
-        TerminalNode w = new TerminalNode(equality4);
-        w.setValue("w");
-        equality4.setRightChild(w);
-        BinaryExpressionNode equality5 = new BinaryExpressionNode(and3);
-        equality5.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode s = new TerminalNode(equality5);
-        s.setValue(new Key<String>("s"));
-        TerminalNode t = new TerminalNode(equality5);
-        t.setValue("t");
-        equality5.setLeftChild(s);
-        equality5.setRightChild(t);
-        and3.setRightChild(equality5);
-        BinaryExpressionNode or3 = new BinaryExpressionNode(and1);
-        BinaryExpressionNode or2 = new BinaryExpressionNode(or3);
-        BinaryExpressionNode or1 = new BinaryExpressionNode(or2);
-        or3.setOperator(BinaryLogicalOperator.OR);
-        or2.setOperator(BinaryLogicalOperator.OR);
-        or1.setOperator(BinaryLogicalOperator.OR);
-        or3.setLeftChild(or2);
-        or2.setLeftChild(or1);
-        BinaryExpressionNode equality1 = new BinaryExpressionNode(or1);
-        or1.setLeftChild(equality1);
-        equality1.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality1);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        equality1.setLeftChild(a);
-        TerminalNode b = new TerminalNode(equality1);
-        b.setValue("b");
-        equality1.setRightChild(b);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(or1);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        NotBooleanExpressionNode not1 = new NotBooleanExpressionNode(or1);
-        not1.setValue(equality2);
-        or1.setRightChild(not1);
-        TerminalNode c = new TerminalNode(equality2);
-        Key<String> cKey = new Key<>("c");
-        c.setValue(cKey);
-        equality2.setLeftChild(c);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue("d");
-        equality2.setRightChild(d);
-        BinaryExpressionNode lessThan = new BinaryExpressionNode(or2);
-        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
-        or2.setRightChild(lessThan);
-        or2.setLeftChild(or1);
-        TerminalNode e = new TerminalNode(lessThan);
-        Key<String> eKey = new Key<>("e");
-        e.setValue(eKey);
-        lessThan.setLeftChild(e);
-        TerminalNode f = new TerminalNode(lessThan);
-        f.setValue("f");
-        lessThan.setRightChild(f);
-        BinaryExpressionNode greaterOrEqual = new BinaryExpressionNode(or3);
-        greaterOrEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
-        TerminalNode g = new TerminalNode(greaterOrEqual);
-        Key<String> gKey = new Key<>("g");
-        g.setValue(gKey);
-        greaterOrEqual.setLeftChild(g);
-        TerminalNode h = new TerminalNode(greaterOrEqual);
-        h.setValue("h");
-        greaterOrEqual.setRightChild(h);
-        or3.setRightChild(greaterOrEqual);
-        or3.setLeftChild(or2);
-        and1.setLeftChild(or3);
-        
-        assertTrue(WhereExpressions.equals(expected, where));
-    }
-    
-    @Test
-    public void testParseQueryWhereAndSortMultiple() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' AND 'c' = ?s OR NOT 'x' >= ?i SORT 'a' ASC , 'b' DSC , 'c' ASC";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
-        assertEquals(2, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNotNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getLimitExpn());
-        assertNotNull(suffixExpn.getWhereExpn());
-        List<SortMember> list = suffixExpn.getSortExpn().getMembers();
-        assertNotNull(list);
-        assertEquals(3, list.size());
-        assertEquals(SortDirection.ASCENDING, list.get(0).getDirection());
-        assertEquals(SortDirection.DESCENDING, list.get(1).getDirection());
-        assertEquals(SortDirection.ASCENDING, list.get(2).getDirection());
-        assertEquals("a", list.get(0).getSortKey());
-        assertEquals("b", list.get(1).getSortKey());
-        assertEquals("c", list.get(2).getSortKey());
-        // build the expected expression tree
-        WhereExpression where = new WhereExpression();
-        BinaryExpressionNode or = new BinaryExpressionNode(where.getRoot());
-        where.getRoot().setValue(or);
-        or.setOperator(BinaryLogicalOperator.OR);
-        BinaryExpressionNode and = new BinaryExpressionNode(or);
-        and.setOperator(BinaryLogicalOperator.AND);
-        or.setLeftChild(and);
-        NotBooleanExpressionNode not = new NotBooleanExpressionNode(or);
-        or.setRightChild(not);
-        BinaryExpressionNode unequality = new BinaryExpressionNode(and);
-        unequality.setOperator(BinaryComparisonOperator.LESS_THAN);
-        TerminalNode a = new TerminalNode(unequality);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        unequality.setLeftChild(a);
-        TerminalNode b = new TerminalNode(unequality);
-        b.setValue("b");
-        unequality.setRightChild(b);
-        and.setLeftChild(unequality);
-        BinaryExpressionNode equality = new BinaryExpressionNode(and);
-        equality.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality);
-        Key<String> cKey = new Key<>("c");
-        c.setValue(cKey);
-        equality.setLeftChild(c);
-        UnfinishedValueNode patch1 = new UnfinishedValueNode();
-        patch1.setParameterIndex(0);
-        patch1.setLHS(false);
-        patch1.setType(String.class);
-        TerminalNode d = new TerminalNode(equality);
-        d.setValue(patch1);
-        equality.setRightChild(d);
-        and.setRightChild(equality);
-        BinaryExpressionNode greaterEqual = new BinaryExpressionNode(not);
-        not.setValue(greaterEqual);
-        greaterEqual.setOperator(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL_TO);
-        TerminalNode x = new TerminalNode(greaterEqual);
-        Key<Integer> xKey = new Key<>("x");
-        x.setValue(xKey);
-        greaterEqual.setLeftChild(x);
-        UnfinishedValueNode patch2 = new UnfinishedValueNode();
-        patch2.setParameterIndex(1);
-        patch2.setLHS(false);
-        patch2.setType(Integer.class);
-        TerminalNode y = new TerminalNode(greaterEqual);
-        y.setValue(patch2);
-        greaterEqual.setRightChild(y);
-        // finally assert equality
-        assertTrue( WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
-    }
-    
-    @Test
-    public void testParseQueryWhereOrSortMultiple() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' OR 'c' = ?s SORT 'a' ASC , ?s DSC , 'c' ASC";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>) parser.parse();
-        assertEquals(2, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNotNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getLimitExpn());
-        assertNotNull(suffixExpn.getWhereExpn());
-        SortExpression sortExp = suffixExpn.getSortExpn();
-        List<SortMember> list = sortExp.getMembers();
-        assertNotNull(list);
-        assertEquals(3, list.size());
-        assertEquals(SortDirection.ASCENDING, list.get(0).getDirection());
-        assertEquals(SortDirection.DESCENDING, list.get(1).getDirection());
-        assertEquals(SortDirection.ASCENDING, list.get(2).getDirection());
-        assertEquals("a", list.get(0).getSortKey());
-        UnfinishedSortKey unfinished = new UnfinishedSortKey();
-        unfinished.setParameterIndex(1);
-        assertEquals(unfinished, list.get(1).getSortKey());
-        assertEquals("c", list.get(2).getSortKey());
-        // build the expected expression tree
-        WhereExpression where = new WhereExpression();
-        BinaryExpressionNode or = new BinaryExpressionNode(where.getRoot());
-        where.getRoot().setValue(or);
-        or.setOperator(BinaryLogicalOperator.OR);
-        BinaryExpressionNode unequality = new BinaryExpressionNode(or);
-        unequality.setOperator(BinaryComparisonOperator.LESS_THAN);
-        TerminalNode a = new TerminalNode(unequality);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        unequality.setLeftChild(a);
-        TerminalNode b = new TerminalNode(unequality);
-        b.setValue("b");
-        unequality.setRightChild(b);
-        or.setLeftChild(unequality);
-        BinaryExpressionNode equality = new BinaryExpressionNode(or);
-        equality.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality);
-        Key<String> cKey = new Key<>("c");
-        c.setValue(cKey);
-        equality.setLeftChild(c);
-        UnfinishedValueNode patch1 = new UnfinishedValueNode();
-        patch1.setParameterIndex(0);
-        patch1.setLHS(false);
-        patch1.setType(String.class);
-        TerminalNode d = new TerminalNode(equality);
-        d.setValue(patch1);
-        equality.setRightChild(d);
-        or.setRightChild(equality);
-        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
-    }
-    
-    @Test
-    public void testParseQuerySimpleWhereAndSimpleSort() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' < 'b' SORT 'a' DSC";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNotNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getLimitExpn());
-        assertNotNull(suffixExpn.getWhereExpn());
-        SortExpression sortExp = suffixExpn.getSortExpn();
-        List<SortMember> list = sortExp.getMembers();
-        assertNotNull(list);
-        assertEquals(1, list.size());
-        assertEquals(SortDirection.DESCENDING, list.get(0).getDirection());
-        assertEquals("a", list.get(0).getSortKey());
-        // build the expected expression tree
-        WhereExpression where = new WhereExpression();
-        BinaryExpressionNode unequality = new BinaryExpressionNode(where.getRoot());
-        where.getRoot().setValue(unequality);
-        unequality.setOperator(BinaryComparisonOperator.LESS_THAN);
-        TerminalNode a = new TerminalNode(unequality);
-        @SuppressWarnings("rawtypes")
-        Key aKey = new Key("a");
-        a.setValue(aKey);
-        unequality.setLeftChild(a);
-        TerminalNode b = new TerminalNode(unequality);
-        b.setValue("b");
-        unequality.setRightChild(b);
-        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
-    }
-    
-    @Test
-    public void testParseQuerySimpleWithOneWhere() {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE '" + Key.AGENT_ID.getName() + "' = ?s";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            fail(e.getMessage());
-        }
-        assertEquals(1, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getLimitExpn());
-        assertNotNull(suffixExpn.getWhereExpn());
-        // build the expected expression tree
-        WhereExpression where = new WhereExpression();
-        BinaryExpressionNode equality = new BinaryExpressionNode(where.getRoot());
-        where.getRoot().setValue(equality);
-        equality.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality);
-        @SuppressWarnings("rawtypes")
-        Key aKey = new Key(Key.AGENT_ID.getName());
-        a.setValue(aKey);
-        equality.setLeftChild(a);
-        TerminalNode b = new TerminalNode(equality);
-        UnfinishedValueNode node = new UnfinishedValueNode();
-        node.setParameterIndex(0);
-        node.setLHS(false);
-        node.setType(String.class);
-        b.setValue(node);
-        equality.setRightChild(b);
-        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
-    }
-
-    @Test
-    public void testParseSimpleWithAndOr() {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE '" + Key.AGENT_ID.getName() + "' = ?s" +
-                            " AND ?s < ?b OR 'a' = 'b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-        assertEquals(3, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getLimitExpn());
-        assertNotNull(suffixExpn.getWhereExpn());
-        // build the expected expression tree
-        WhereExpression where = new WhereExpression();
-        BinaryExpressionNode or = new BinaryExpressionNode(where.getRoot());
-        where.getRoot().setValue(or);
-        or.setOperator(BinaryLogicalOperator.OR);
-        BinaryExpressionNode and = new BinaryExpressionNode(or);
-        and.setOperator(BinaryLogicalOperator.AND);
-        or.setLeftChild(and);
-        BinaryExpressionNode equality = new BinaryExpressionNode(and);
-        equality.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality);
-        Key<String> aKey = new Key<>("a");
-        a.setValue(aKey);
-        equality.setLeftChild(a);
-        TerminalNode b = new TerminalNode(equality);
-        b.setValue("b");
-        equality.setRightChild(b);
-        or.setRightChild(equality);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(and);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality2);
-        Key<String> cKey = new Key<>(Key.AGENT_ID.getName());
-        c.setValue(cKey);
-        equality2.setLeftChild(c);
-        UnfinishedValueNode patch1 = new UnfinishedValueNode();
-        patch1.setParameterIndex(0);
-        patch1.setLHS(false);
-        patch1.setType(String.class);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue(patch1);
-        equality2.setRightChild(d);
-        and.setLeftChild(equality2);
-        BinaryExpressionNode lessThan = new BinaryExpressionNode(and);
-        lessThan.setOperator(BinaryComparisonOperator.LESS_THAN);
-        UnfinishedValueNode patch = new UnfinishedValueNode();
-        patch.setParameterIndex(1);
-        patch.setType(String.class);
-        patch.setLHS(true);
-        TerminalNode x = new TerminalNode(lessThan);
-        x.setValue(patch);
-        lessThan.setLeftChild(x);
-        UnfinishedValueNode patch2 = new UnfinishedValueNode();
-        patch2.setLHS(false);
-        patch2.setParameterIndex(2);
-        patch2.setType(Boolean.class);
-        TerminalNode y = new TerminalNode(lessThan);
-        y.setValue(patch2);
-        lessThan.setRightChild(y);
-        and.setRightChild(lessThan);
-        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
-    }
-
-    @Test
-    public void testParseSimpleWithAnd() {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?s AND ?s = 'd'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-        assertEquals(2, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getLimitExpn());
-        assertNotNull(suffixExpn.getWhereExpn());
-        // build the expected expression tree
-        WhereExpression where = new WhereExpression();
-        BinaryExpressionNode and = new BinaryExpressionNode(where.getRoot());
-        where.getRoot().setValue(and);
-        and.setOperator(BinaryLogicalOperator.AND);
-        BinaryExpressionNode equality = new BinaryExpressionNode(and);
-        equality.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality);
-        @SuppressWarnings("rawtypes")
-        Key aKey = new Key("a");
-        a.setValue(aKey);
-        equality.setLeftChild(a);
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setParameterIndex(0);
-        unfinished.setLHS(false);
-        unfinished.setType(String.class);
-        TerminalNode b = new TerminalNode(equality);
-        b.setValue(unfinished);
-        equality.setRightChild(b);
-        and.setLeftChild(equality);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(and);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality2);
-        UnfinishedValueNode patch1 = new UnfinishedValueNode();
-        patch1.setParameterIndex(1);
-        patch1.setType(String.class);
-        patch1.setLHS(true);
-        c.setValue(patch1);
-        equality2.setLeftChild(c);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue("d");
-        equality2.setRightChild(d);
-        and.setRightChild(equality2);
-        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
-    }
-
-    @Test
-    public void testParseSimpleWithNotOR() {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE NOT 'a' = ?s OR ?s = 'd'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-        assertEquals(2, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getLimitExpn());
-        assertNotNull(suffixExpn.getWhereExpn());
-        // build the expected parse tree
-        WhereExpression expn = new WhereExpression();
-        BinaryExpressionNode or = new BinaryExpressionNode(expn.getRoot());
-        expn.getRoot().setValue(or);
-        or.setOperator(BinaryLogicalOperator.OR);
-        NotBooleanExpressionNode notNode = new NotBooleanExpressionNode(or);
-        BinaryExpressionNode comparison = new BinaryExpressionNode(notNode);
-        notNode.setValue(comparison);
-        or.setLeftChild(notNode);
-        comparison.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode rightCompTerm = new TerminalNode(comparison);
-        TerminalNode leftCompTerm = new TerminalNode(comparison);
-        @SuppressWarnings("rawtypes")
-        Key aKey = new Key("a");
-        leftCompTerm.setValue(aKey);
-        UnfinishedValueNode patch1 = new UnfinishedValueNode();
-        patch1.setParameterIndex(0);
-        patch1.setType(String.class);
-        patch1.setLHS(false);
-        rightCompTerm.setValue(patch1);
-        comparison.setLeftChild(leftCompTerm);
-        comparison.setRightChild(rightCompTerm);
-        BinaryExpressionNode otherComparison = new BinaryExpressionNode(or);
-        otherComparison.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode leftUnfinished = new TerminalNode(otherComparison);
-        UnfinishedValueNode patch2 = new UnfinishedValueNode();
-        patch2.setParameterIndex(1);
-        patch2.setLHS(true);
-        patch2.setType(String.class);
-        leftUnfinished.setValue(patch2);
-        TerminalNode other = new TerminalNode(otherComparison);
-        other.setValue("d");
-        otherComparison.setLeftChild(leftUnfinished);
-        otherComparison.setRightChild(other);
-        or.setRightChild(otherComparison);
-        assertTrue(WhereExpressions.equals(expn, suffixExpn.getWhereExpn()));
-    }
-
-    @Test
-    public void testParseSimpleWithOr() {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?s OR ?s = 'd'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-        assertEquals(2, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getLimitExpn());
-        assertNotNull(suffixExpn.getWhereExpn());
-        // build the expected parse tree
-        WhereExpression where = new WhereExpression();
-        BinaryExpressionNode and = new BinaryExpressionNode(where.getRoot());
-        where.getRoot().setValue(and);
-        and.setOperator(BinaryLogicalOperator.OR);
-        BinaryExpressionNode equality = new BinaryExpressionNode(and);
-        equality.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode a = new TerminalNode(equality);
-        @SuppressWarnings("rawtypes")
-        Key aKey = new Key("a");
-        a.setValue(aKey);
-        equality.setLeftChild(a);
-        UnfinishedValueNode unfinished = new UnfinishedValueNode();
-        unfinished.setParameterIndex(0);
-        unfinished.setType(String.class);
-        unfinished.setLHS(false);
-        TerminalNode b = new TerminalNode(equality);
-        b.setValue(unfinished);
-        equality.setRightChild(b);
-        and.setLeftChild(equality);
-        BinaryExpressionNode equality2 = new BinaryExpressionNode(and);
-        equality2.setOperator(BinaryComparisonOperator.EQUALS);
-        TerminalNode c = new TerminalNode(equality2);
-        UnfinishedValueNode patch1 = new UnfinishedValueNode();
-        patch1.setParameterIndex(1);
-        patch1.setType(String.class);
-        patch1.setLHS(true);
-        c.setValue(patch1);
-        equality2.setLeftChild(c);
-        TerminalNode d = new TerminalNode(equality2);
-        d.setValue("d");
-        equality2.setRightChild(d);
-        and.setRightChild(equality2);
-        assertTrue(WhereExpressions.equals(where, suffixExpn.getWhereExpn()));
-    }
-    
-    @Test
-    public void testParseSimpleWithLimit() {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT 1";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-        assertEquals(0, statement.getNumParams());
-        assertEquals(mockQuery.getClass().getName(), statement.getRawStatement().getClass().getName());
-        SuffixExpression suffixExpn = statement.getSuffixExpression();
-        assertNull(suffixExpn.getSortExpn());
-        assertNull(suffixExpn.getWhereExpn());
-        assertNotNull(suffixExpn.getLimitExpn());
-        assertEquals(1, suffixExpn.getLimitExpn().getValue());
-    }
-    
-    @Test
-    public void testParseAddBasic() throws DescriptorParsingException {
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = ?s";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-        assertTrue(statement.getRawStatement() instanceof Add);
-        SuffixExpression suffix = statement.getSuffixExpression();
-        assertNotNull(suffix);
-        WhereExpression where = suffix.getWhereExpn();
-        LimitExpression limit = suffix.getLimitExpn();
-        SortExpression sort = suffix.getSortExpn();
-        assertNull(where);
-        assertNull(limit);
-        assertNull(sort);
-        SetList setList = statement.getSetList();
-        assertNotNull(setList);
-        List<SetListValue> valuesList = setList.getValues();
-        assertEquals(2, valuesList.size());
-        SetListValue first = valuesList.get(0);
-        SetListValue second = valuesList.get(1);
-        TerminalNode a = new TerminalNode(null);
-        a.setValue(new Key<>("a"));
-        TerminalNode b = new TerminalNode(null);
-        b.setValue("b");
-        SetListValue firstExpected = new SetListValue();
-        firstExpected.setKey(a);
-        firstExpected.setValue(b);
-        assertEquals(firstExpected, first);
-        TerminalNode c = new TerminalNode(null);
-        c.setValue(new Key<>("c"));
-        UnfinishedValueNode dVal = new UnfinishedValueNode();
-        dVal.setType(String.class);
-        dVal.setLHS(false);
-        dVal.setParameterIndex(0);
-        TerminalNode d = new TerminalNode(null);
-        d.setValue(dVal);
-        SetListValue secondExpected = new SetListValue();
-        secondExpected.setKey(c);
-        secondExpected.setValue(d);
-        assertEquals(secondExpected, second);
-    }
-    
-    @Test
-    public void testParseUpdateBasic() throws DescriptorParsingException {
-        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = ?s WHERE 'foo' != ?i";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        ParsedStatementImpl<AgentInformation> statement = null; 
-        try {
-            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
-        } catch (DescriptorParsingException e) {
-            e.printStackTrace();
-            fail(e.getMessage());
-        }
-        assertTrue(statement.getRawStatement() instanceof Update);
-        SuffixExpression suffix = statement.getSuffixExpression();
-        assertNotNull(suffix);
-        WhereExpression where = suffix.getWhereExpn();
-        LimitExpression limit = suffix.getLimitExpn();
-        SortExpression sort = suffix.getSortExpn();
-        assertNotNull(where);
-        assertNull(limit);
-        assertNull(sort);
-        SetList setList = statement.getSetList();
-        assertNotNull(setList);
-        List<SetListValue> valuesList = setList.getValues();
-        assertEquals(2, valuesList.size());
-        SetListValue first = valuesList.get(0);
-        SetListValue second = valuesList.get(1);
-        TerminalNode a = new TerminalNode(null);
-        a.setValue(new Key<>("a"));
-        TerminalNode b = new TerminalNode(null);
-        b.setValue("b");
-        SetListValue firstExpected = new SetListValue();
-        firstExpected.setKey(a);
-        firstExpected.setValue(b);
-        assertEquals(firstExpected, first);
-        TerminalNode c = new TerminalNode(null);
-        c.setValue(new Key<>("c"));
-        UnfinishedValueNode dVal = new UnfinishedValueNode();
-        dVal.setType(String.class);
-        dVal.setLHS(false);
-        dVal.setParameterIndex(0);
-        TerminalNode d = new TerminalNode(null);
-        d.setValue(dVal);
-        SetListValue secondExpected = new SetListValue();
-        secondExpected.setKey(c);
-        secondExpected.setValue(d);
-        assertEquals(secondExpected, second);
-        // Build expected where expn
-        WhereExpression expectedWhere = new WhereExpression();
-        BinaryExpressionNode equality = new BinaryExpressionNode(expectedWhere.getRoot());
-        expectedWhere.getRoot().setValue(equality);
-        equality.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
-        TerminalNode foo = new TerminalNode(equality);
-        @SuppressWarnings("rawtypes")
-        Key fooKey = new Key("foo");
-        foo.setValue(fooKey);
-        equality.setLeftChild(foo);
-        TerminalNode fooVal = new TerminalNode(equality);
-        UnfinishedValueNode node = new UnfinishedValueNode();
-        node.setParameterIndex(1);
-        node.setLHS(false);
-        node.setType(Integer.class);
-        fooVal.setValue(node);
-        equality.setRightChild(fooVal);
-        assertTrue(WhereExpressions.equals(expectedWhere, where));
-    }
-    
-    // TODO: Add basic parse tests for REMOVE/REPLACE
-    
-    @Test
-    public void rejectLongValAsIntType() throws DescriptorParsingException {
-        // 30000000003 > Integer.MAX_VALUE; needs to be preceded by 'l/L'
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000000003";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("Illegal terminal type."));
-        }
-    }
-
-    @Test
-    public void rejectLimitWhichIsNotInt() {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT illegal";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            assertEquals("Invalid limit expression. 'illegal' not an integer", e.getMessage());
-        }
-    }
-
-    @Test
-    public void rejectLHSnotString() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE a < 1";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("Expected string value. Got term ->a<-"));
-        }
-    }
-    
-    /*
-     * At this point list types don't make sense in WHERE.
-     *   What should "'a' != [ 'a', 'b' ]" evaluate to?
-     *   How about this? "'key' > [ 1, 2 ]"
-     * 
-     * The only reasonable use case in WHERE would be 'a' IN [ 'a', 'b' ... ].
-     * We don't support this in a prepared context at this point. Should this
-     * change in future, this test needs to be carefully re-crafted. 
-     */
-    @Test
-    public void rejectListTypesAsFreeVarInInvalidContext() throws DescriptorParsingException {
-        List<String> illegalDescs = new ArrayList<>();
-
-        // Where should not support list types/Pojos.
-        String[] illegalWheres = new String[] {
-                " WHERE 'a' = ?s[",
-                " WHERE 'a' = ?i[",
-                " WHERE 'a' = ?p[",
-                " WHERE 'a' = ?d[",
-        };
-        
-        // Make sure we test for QUERY, QUERY-COUNT, REPLACE, UPDATE, REMOVE
-        // i.e. all that accept a WHERE.
-        String basicQuery = "QUERY " + AgentInfoDAO.CATEGORY.getName();
-        String basicQueryCount = "QUERY-COUNT " + AgentInfoDAO.CATEGORY.getName();
-        // note SET clause is legal
-        String basicUpdate = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?s[";
-        // note SET clause is legal
-        String basicReplace = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = ?i[";
-        String basicRemove = "REMOVE " + AgentInfoDAO.CATEGORY.getName();
-        for (String where: illegalWheres) {
-            illegalDescs.add(basicQuery + where);
-            illegalDescs.add(basicQueryCount + where);
-            illegalDescs.add(basicUpdate + where);
-            illegalDescs.add(basicReplace + where);
-            illegalDescs.add(basicRemove + where);
-        }
-        
-        // Test all illegal descs involving WHERE
-        doIllegalDescsTest(illegalDescs, "WHERE", "List");
-        
-        // Test limit too
-        illegalDescs.clear();
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?i[");
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?s[");
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?p[");
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?d[");
-        
-        doIllegalDescsTest(illegalDescs, "LIMIT", "List");
-        
-        // Test sort
-        illegalDescs.clear();
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?i[ ASC");
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?s[ ASC");
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?p[ ASC");
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?d[ ASC");
-        
-        doIllegalDescsTest(illegalDescs, "SORT", "List");
-    }
-    
-    /*
-     * Pojos don't make sense in a WHERE, LIMIT and SORT clause. This test makes
-     * sure we reject this right away.
-     * 
-     */
-    @Test
-    public void rejectPojoTypesAsFreeVarInInvalidContext() throws DescriptorParsingException {
-        List<String> illegalDescs = new ArrayList<>();
-        
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?p");
-        doIllegalDescsTest(illegalDescs, "WHERE", "Pojo");
-        illegalDescs.clear();
-        
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " LIMIT ?p");
-        doIllegalDescsTest(illegalDescs, "LIMIT", "Pojo");
-        illegalDescs.clear();
-        
-        illegalDescs.add("QUERY " + AgentInfoDAO.CATEGORY.getName() + " SORT ?p DSC");
-        doIllegalDescsTest(illegalDescs, "SORT", "Pojo");
-    }
-    
-    private void doIllegalDescsTest(List<String> descs, String errorMsgContext, String type) {
-        for (String strDesc : descs) {
-            StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, strDesc); 
-            parser = new StatementDescriptorParser<>(storage, desc);
-            try {
-                parser.parse();
-                fail(strDesc + " should not parse");
-            } catch (DescriptorParsingException e) {
-                // pass
-                assertEquals(strDesc + " did not provide correct error message.",
-                        type + " free variable type not allowed in " + errorMsgContext + " context", e.getMessage());
-            }
-        }
-    }
-    
-    @Test
-    public void rejectIllegalFreeParamType() throws DescriptorParsingException {
-        // ? should be one of '?i', '?l', '?s', '?b', '?s['
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE ? < 1";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertEquals("Unknown type of free parameter: '?'", e.getMessage());
-        }
-    }
-    
-    @Test
-    public void rejectParseQueryWhereAndSortMultipleIllegalSortModifier() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'somekey' < 2 AND 'c' = ?s OR 'a' >= ?i SORT 'a' ASC , 'b' ILLEGAL , 'c' ASC";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("Expected ASC or DSC"));
-        }
-    }
-
-    @Test
-    public void rejectParseQueryWhereBoolTerm() throws DescriptorParsingException {
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE true AND false";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-        }
-    }
-    
-    @Test
-    public void rejectQueryWithParenthesis() throws DescriptorParsingException {
-        // We don't allow parenthesized expressions. This is due to mongodb not
-        // allowing this.
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE NOT ( 'a' = 'b' AND 'c' = 'd' )";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-        }
-    }
-
-    @Test
-    public void rejectSimpleQueryWithMissingSpaces() throws DescriptorParsingException {
-        // we require a space before every operator/keyword
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY + " WHERE " + "'a'='b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-        }
-    }
-    
-    @Test
-    public void rejectSimpleQueryWithMissingSpaces2() throws DescriptorParsingException {
-        // we require a space before every operator/keyword
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY + " WHERE " + "'a' ='b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-        }
-    }
-    
-    @Test
-    public void rejectSimpleQueryWithInvalidComparison() throws DescriptorParsingException {
-        // <> is illegal
-        String descrString = "QUERY " + AgentInfoDAO.CATEGORY + " WHERE " + "'a' <> 'b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descrString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("should not parse");
-        } catch (DescriptorParsingException e) {
-            // pass
-        }
-    }
-    
-    @Test
-    public void rejectInvalidDescriptorStringBadWhere() throws DescriptorParsingException {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " where '" + Key.AGENT_ID.getName() + "'= ?s";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("lower case where not allowed in descriptor. Should have rejected.");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("Unexpected token: 'where'"));
-        }
-    }
-    
-    @Test
-    public void rejectInvalidDescriptorStringBadStatementType() throws DescriptorParsingException {
-        String descString = "UNKNOWN some-unknown-category WHERE 1 = ?i";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("UNKNOWN not a valid statement type");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("Unknown statement type"));
-        }
-    }
-    
-    @Test
-    public void rejectInvalidDescriptorStringCategoryMismatch() throws DescriptorParsingException {
-        String descString = "QUERY some-unknown-category WHERE 1 = ?i";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("category names in descriptor and Category object did not match!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("Category mismatch"));
-        }
-    }
-    
-    @Test
-    public void rejectInvalidDescriptorStringBadSortNoArg() throws DescriptorParsingException {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = ?i SORT";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("category names in descriptor and Category object did not match!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("Invalid where clause"));
-        }
-    }
-    
-    @Test
-    public void rejectInvalidDescriptorStringBadWhereNoArg() throws DescriptorParsingException {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE SORT";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("category names in descriptor and Category object did not match!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("SORT"));
-            assertTrue(e.getMessage().contains("Expected string value"));
-        }
-    }
-    
-    @Test
-    public void rejectAddWithInvalidSetList() throws DescriptorParsingException {
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = , 'c' = 'd'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("Invalid SET values list.");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertEquals("Illegal terminal type. Token was ->,<-", e.getMessage());
-        }
-    }
-    
-    @Test
-    public void rejectAddWithWhere() throws DescriptorParsingException {
-        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd' WHERE 'a' = 'b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("ADD operation does not support WHERE clauses!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("WHERE clause not allowed for ADD"));
-        }
-    }
-    
-    @Test
-    public void rejectReplaceWithoutWhere() throws DescriptorParsingException {
-        String descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("REPLACE operation requires WHERE clause, and should not parse!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("WHERE clause required for REPLACE"));
-        }
-    }
-    
-    @Test
-    public void rejectUpdateWithoutWhere() throws DescriptorParsingException {
-        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("UPDATE operation requires WHERE clause, and should not parse!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("WHERE clause required for UPDATE"));
-        }
-    }
-    
-    @Test
-    public void rejectRemoveWithSetList() throws DescriptorParsingException {
-        String descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("REMOVE does not allow SET list, and should not parse!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("SET not allowed for REMOVE"));
-        }
-    }
-    
-    @Test
-    public void rejectQueryWithSetList() throws DescriptorParsingException {
-        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("QUERY does not allow SET list, and should not parse!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertTrue(e.getMessage().contains("SET not allowed for QUERY"));
-        }
-    }
-    
-    
-    private void doRejectWriteSortLimitTest(String descString, String failmsg) {
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail(failmsg);
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertEquals("LIMIT/SORT only allowed for QUERY/QUERY-COUNT", e.getMessage());
-        }
-    }
-    
-    @Test
-    public void rejectWritesWithSortLimit() throws DescriptorParsingException {
-        // Update rejects
-        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' SORT 'a' DSC";
-        String failmsg = "SORT in UPDATE is not allowed, and should not parse!";
-        doRejectWriteSortLimitTest(descString, failmsg);
-        descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' LIMIT 1";
-        failmsg = "LIMIT in UPDATE is not allowed, and should not parse!";
-        doRejectWriteSortLimitTest(descString, failmsg);
-        
-        // Remove rejects
-        descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' LIMIT 1";
-        failmsg = "LIMIT in REMOVE is not allowed, and should not parse!";
-        doRejectWriteSortLimitTest(descString, failmsg);
-        descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' SORT 'a' ASC";
-        failmsg = "SORT in REMOVE is not allowed, and should not parse!";
-        doRejectWriteSortLimitTest(descString, failmsg);
-        
-        // Replace rejects
-        descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' LIMIT 1";
-        failmsg = "LIMIT in REPLACE is not allowed, and should not parse!";
-        doRejectWriteSortLimitTest(descString, failmsg);
-        descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' SORT 'a' ASC";
-        failmsg = "SORT in REPLACE is not allowed, and should not parse!";
-        doRejectWriteSortLimitTest(descString, failmsg);
-        
-        // Add rejects
-        descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' LIMIT 1";
-        failmsg = "LIMIT in ADD is not allowed, and should not parse!";
-        doRejectWriteSortLimitTest(descString, failmsg);
-        descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' SORT 'a' ASC";
-        failmsg = "SORT in ADD is not allowed, and should not parse!";
-        doRejectWriteSortLimitTest(descString, failmsg);
-    }
-    
-    @Test
-    public void rejectUpdateWithoutSet() throws DescriptorParsingException {
-        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b'";
-        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
-        parser = new StatementDescriptorParser<>(storage, desc);
-        try {
-            parser.parse();
-            fail("UPDATE requires SET list, and should not parse!");
-        } catch (DescriptorParsingException e) {
-            // pass
-            assertEquals("SET list required for UPDATE", e.getMessage());
-        }
-    }
-    
-    // TODO: add tests where set list does not match all props of Pojo class for
-    // add/replace
-    
-}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/WhereExpressions.java	Mon Oct 07 15:03:04 2013 -0400
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/WhereExpressions.java	Fri Oct 04 18:13:31 2013 +0200
@@ -49,7 +49,7 @@
 /**
  * Helper class for comparing where expression trees.
  *
- * @see StatementDescriptorParserTest
+ * @see BasicDescriptorParserTest
  */
 public class WhereExpressions {