changeset 1152:4ba43dcff78e

Implement prepared statements (Part 1). Reviewed-by: ebaron Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-June/007214.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Fri, 28 Jun 2013 15:27:50 +0200
parents 2b3cf603245d
children fe3f66cc83e7
files storage/core/pom.xml storage/core/src/main/java/com/redhat/thermostat/storage/core/DataModifyingStatement.java storage/core/src/main/java/com/redhat/thermostat/storage/core/DescriptorParsingException.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatement.java storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatementFactory.java storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java storage/core/src/main/java/com/redhat/thermostat/storage/core/Statement.java storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementDescriptor.java storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementExecutionException.java storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/AbstractUnfinished.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BinaryExpressionNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/IllegalPatchException.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Node.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/NotBooleanExpressionNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Operator.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatement.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Patchable.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedExpression.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedSortMember.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedWhereExpressionImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedParameter.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/Printable.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortExpression.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.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/SuffixExpression.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnaryExpressionNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Unfinished.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedLimitValue.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedSortKey.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java
diffstat 40 files changed, 3033 insertions(+), 16 deletions(-) [+]
line wrap: on
line diff
--- a/storage/core/pom.xml	Wed Jul 03 12:12:47 2013 -0600
+++ b/storage/core/pom.xml	Fri Jun 28 15:27:50 2013 +0200
@@ -82,6 +82,7 @@
               com.redhat.thermostat.storage.internal.dao,
               com.redhat.thermostat.storage.internal,
               com.redhat.thermostat.storage.internal.test,
+              com.redhat.thermostat.storage.internal.statement,
             </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/DataModifyingStatement.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,17 @@
+package com.redhat.thermostat.storage.core;
+
+/**
+ * Marker interface for {@link Statement}s which perform write operations on
+ * storage. These statements usually only return success/failure responses or
+ * more specific error codes.
+ *
+ */
+public interface DataModifyingStatement extends Statement {
+
+    /**
+     * Executes this statement.
+     * 
+     * @return Zero on success. A non-zero failure code otherwise.
+     */
+    int execute();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/DescriptorParsingException.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,9 @@
+package com.redhat.thermostat.storage.core;
+
+@SuppressWarnings("serial")
+public class DescriptorParsingException extends Exception {
+
+    public DescriptorParsingException(String msg) {
+        super(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatement.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,52 @@
+package com.redhat.thermostat.storage.core;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+
+/**
+ * A prepared statement.
+ * 
+ * @see Statement
+ * @see DataModifyingStatement
+ * @see Storage#prepareStatement(StatementDescriptor)
+ *
+ */
+public interface PreparedStatement {
+    
+    void setBoolean(int paramIndex, boolean paramValue);
+    
+    void setLong(int paramIndex, long paramValue);
+    
+    void setInt(int paramIndex, int paramValue);
+    
+    void setString(int paramIndex, String paramValue);
+    
+    void setStringList(int paramIndex, String[] paramValue);
+
+    /**
+     * Executes a predefined {@link DataModifyingStatement}.
+     * 
+     * @return a non-zero error code on failure. Zero otherwise.
+     * 
+     * @throws StatementExecutionException
+     *             If the prepared statement wasn't valid for execution.
+     */
+    int execute() throws StatementExecutionException;
+
+    /**
+     * Executes a predefined {@link Query}.
+     * 
+     * @return a {@link Cursor} as a result to the underlying {@link Query}
+     * 
+     * @throws StatementExecutionException
+     *             If the prepared statement wasn't valid for execution.
+     */
+    <T extends Pojo> Cursor<T> executeQuery() throws StatementExecutionException;
+    
+    /**
+     * 
+     * @return The unique ID of this predefined statement for the underlying
+     *         {@link Storage}.
+     */
+    int getId();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/PreparedStatementFactory.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,18 @@
+package com.redhat.thermostat.storage.core;
+
+import com.redhat.thermostat.storage.internal.statement.PreparedStatementImpl;
+
+/**
+ * Factory for instantiating a {@link PreparedStatement}.
+ *
+ */
+public class PreparedStatementFactory {
+
+    public static PreparedStatement getInstance(Storage storage,
+            StatementDescriptor desc) throws DescriptorParsingException {
+        // This is the sole method in order to avoid leaking impl details of
+        // this OSGi module. Storage implementations will have to use this
+        // factory.
+        return new PreparedStatementImpl(storage, desc);
+    }
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Wed Jul 03 12:12:47 2013 -0600
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Query.java	Fri Jun 28 15:27:50 2013 +0200
@@ -42,7 +42,7 @@
 /**
  * Describes what data should be fetched.
  */
-public interface Query<T extends Pojo> {
+public interface Query<T extends Pojo> extends Statement {
 
     enum SortDirection {
         ASCENDING(1),
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Wed Jul 03 12:12:47 2013 -0600
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/QueuedStorage.java	Fri Jun 28 15:27:50 2013 +0200
@@ -203,10 +203,7 @@
 
     }
 
-    <T extends Pojo> Cursor<T> findAllPojos(Query query, Class<T> resultClass) {
-        return query.execute();
-    }
-
+    @SuppressWarnings("rawtypes") 
     @Override
     public long getCount(Category category) {
         return delegate.getCount(category);
@@ -270,6 +267,12 @@
     public void registerCategory(final Category<?> category) {
         delegate.registerCategory(category);
     }
+    
+    @Override
+    public PreparedStatement prepareStatement(final StatementDescriptor desc)
+            throws DescriptorParsingException {
+        return delegate.prepareStatement(desc);
+    }
 
     @Override
     public Connection getConnection() {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Statement.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,10 @@
+package com.redhat.thermostat.storage.core;
+
+/**
+ * Marker interface for all operations on storage. This includes queries and
+ * statements manipulating data.
+ *
+ */
+public interface Statement {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementDescriptor.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,18 @@
+package com.redhat.thermostat.storage.core;
+
+public interface StatementDescriptor {
+
+    /**
+     * Describes this statement for preparation. For example:
+     * 
+     * <pre>
+     * Query host-info where agentId = ?
+     * </pre>
+     * 
+     * @return The statement descriptor.
+     */
+    String getQueryDescriptor();
+    
+    Category<?> getCategory();
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementExecutionException.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,14 @@
+package com.redhat.thermostat.storage.core;
+
+/**
+ * Exception thrown if something was wrong with a {@link PreparedStatement}
+ * and it was attempted to execute it.
+ *
+ */
+@SuppressWarnings("serial")
+public class StatementExecutionException extends Exception {
+
+    public StatementExecutionException(Throwable cause) {
+        super(cause);
+    }
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Wed Jul 03 12:12:47 2013 -0600
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/Storage.java	Fri Jun 28 15:27:50 2013 +0200
@@ -55,6 +55,17 @@
     String getAgentId();
 
     void registerCategory(Category<?> category);
+    
+    /**
+     * Prepares the given statement for execution.
+     * 
+     * @param desc The statement descriptor to prepare.
+     * @return A {@link PreparedStatement} if the given statement was
+     *         something Storage knows about, {@code null} otherwise.
+     * @throws DescriptorParsingException If the descriptor string was invalid.
+     */
+    PreparedStatement prepareStatement(StatementDescriptor desc)
+            throws DescriptorParsingException;
 
     /**
      * Returns the Connection object that may be used to manage connections
@@ -85,7 +96,6 @@
     Update createUpdate(Category<?> category);
     Remove createRemove();
 
-
     void shutdown();
 
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java	Wed Jul 03 12:12:47 2013 -0600
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java	Fri Jun 28 15:27:50 2013 +0200
@@ -37,14 +37,22 @@
 package com.redhat.thermostat.storage.internal.dao;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
+import java.util.logging.Logger;
 
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedStatement;
 import com.redhat.thermostat.storage.core.Put;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.StatementExecutionException;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
@@ -54,6 +62,7 @@
 
 public class AgentInfoDAOImpl implements AgentInfoDAO {
 
+    private static final Logger logger = LoggingUtils.getLogger(AgentInfoDAOImpl.class);
     private final Storage storage;
     private final ExpressionFactory factory;
 
@@ -70,9 +79,22 @@
 
     @Override
     public List<AgentInformation> getAllAgentInformation() {
-        Query<AgentInformation> query = storage.createQuery(CATEGORY);
-        Cursor<AgentInformation> agentCursor = query.execute();
-
+        String allAgentsQuery = "QUERY " + CATEGORY.getName();
+        AgentInformationDescriptor desc = new AgentInformationDescriptor(CATEGORY, allAgentsQuery);
+        PreparedStatement prepared = null;
+        Cursor<AgentInformation> agentCursor = null;
+        try {
+            prepared = storage.prepareStatement(desc);
+            agentCursor = prepared.executeQuery();
+        } catch (DescriptorParsingException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.severe("Preparing query '" + desc + "' failed!");
+            return Collections.emptyList();
+        } catch (StatementExecutionException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.severe("Executing query '" + desc + "' failed!");
+            return Collections.emptyList();
+        }
         List<AgentInformation> results = new ArrayList<>();
 
         while (agentCursor.hasNext()) {
@@ -84,13 +106,25 @@
 
     @Override
     public List<AgentInformation> getAliveAgents() {
-        Query<AgentInformation> query = storage.createQuery(CATEGORY);
-        ExpressionFactory factory = new ExpressionFactory();
-        Expression expr = factory.equalTo(ALIVE_KEY, Boolean.TRUE);
-        query.where(expr);
-
-        Cursor<AgentInformation> agentCursor = query.execute();
-
+        // QUERY agent-config WHERE ? = true
+        String allAgentsQuery = "QUERY " + CATEGORY.getName() + " WHERE ?s = ?b";
+        AgentInformationDescriptor desc = new AgentInformationDescriptor(CATEGORY, allAgentsQuery);
+        PreparedStatement prepared = null;
+        Cursor<AgentInformation> agentCursor = null;
+        try {
+            prepared = storage.prepareStatement(desc);
+            prepared.setString(0, ALIVE_KEY.getName());
+            prepared.setBoolean(1, true);
+            agentCursor = prepared.executeQuery();
+        } catch (DescriptorParsingException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.severe("Preparing query '" + desc + "' failed!");
+            return Collections.emptyList();
+        } catch (StatementExecutionException e) {
+            // should not happen, but if it *does* happen, at least log it
+            logger.severe("Executing query '" + desc + "' failed!");
+            return Collections.emptyList();
+        }
         List<AgentInformation> results = new ArrayList<>();
 
         while (agentCursor.hasNext()) {
@@ -134,6 +168,33 @@
         update.set(CONFIG_LISTEN_ADDRESS, agentInfo.getConfigListenAddress());
         update.apply();
     }
+    
+    private static class AgentInformationDescriptor implements StatementDescriptor {
+        
+        private final Category<AgentInformation> category;
+        private final String desc;
+        
+        private AgentInformationDescriptor(Category<AgentInformation> cat, String desc) {
+            this.category = cat;
+            this.desc = desc;
+        }
+        
+        @Override
+        public Category<?> getCategory() {
+            return category;
+        }
+
+        @Override
+        public String getQueryDescriptor() {
+            return desc;
+        }
+        
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + " " + desc;
+        }
+        
+    }
 
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/AbstractUnfinished.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,64 @@
+/*
+ * 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.Objects;
+
+/**
+ * Abstract superclass for unfinished nodes (i.e. nodes in the prepared
+ * statements parse tree which need to be patched with their real values).
+ * 
+ */
+abstract class AbstractUnfinished implements Unfinished {
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == null) {
+            return false;
+        }
+        if (!(other instanceof Unfinished)) {
+            return false;
+        }
+        Unfinished o = (Unfinished)other;
+        return getParameterIndex() == o.getParameterIndex();
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(getParameterIndex());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/BinaryExpressionNode.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,163 @@
+/*
+ * 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.Objects;
+
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.query.BinaryComparisonExpression;
+import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
+import com.redhat.thermostat.storage.query.BinaryLogicalExpression;
+import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.LiteralExpression;
+
+
+class BinaryExpressionNode extends Node {
+    
+    private Node leftChild;
+    private Node rightChild;
+    private Object operator;
+
+    public Object getOperator() {
+        return operator;
+    }
+
+    public void setOperator(Object operator) {
+        this.operator = operator;
+    }
+
+    BinaryExpressionNode(Node parent) {
+        super(parent);
+    }
+
+    public Node getLeftChild() {
+        return leftChild;
+    }
+
+    public void setLeftChild(Node leftChild) {
+        this.leftChild = leftChild;
+    }
+
+    public Node getRightChild() {
+        return rightChild;
+    }
+
+    public void setRightChild(Node rightChild) {
+        this.rightChild = rightChild;
+    }
+    
+    @Override
+    public PatchedWhereExpression patch(PreparedParameter[] params) throws IllegalPatchException {
+        if (leftChild == null || rightChild == null || getOperator() == null) {
+            String msg = BinaryExpressionNode.class.getSimpleName() +
+                    " invalid when attempted to patch";
+            IllegalStateException cause = new IllegalStateException(msg);
+            throw new IllegalPatchException(cause);
+        }
+        PatchedWhereExpression left = leftChild.patch(params);
+        PatchedWhereExpression right = rightChild.patch(params);
+        
+        Expression leftExpression = left.getExpression();
+        Expression rightExpression = right.getExpression();
+        return createExpression(leftExpression, rightExpression);
+    }
+
+    private PatchedWhereExpression createExpression(Expression leftExpression,
+            Expression rightExpression) {
+        assert( getOperator() != null );
+        if (getOperator() instanceof BinaryComparisonOperator) {
+            BinaryComparisonOperator op = (BinaryComparisonOperator)getOperator();
+            return getBinaryComparisonExpression(leftExpression, op, rightExpression);
+        } else if (getOperator() instanceof BinaryLogicalOperator) {
+            BinaryLogicalOperator op = (BinaryLogicalOperator)getOperator();
+            return getBinaryLogicalExpression(leftExpression, op, rightExpression);
+        }
+        return null;
+    }
+    
+    private PatchedWhereExpression getBinaryLogicalExpression(Expression a,
+            BinaryLogicalOperator op, Expression b) {
+        BinaryLogicalExpression<Expression, Expression> impl = new BinaryLogicalExpression<Expression, Expression>(
+                a, op, b);
+        return new PatchedWhereExpressionImpl(impl);
+    }
+
+    @SuppressWarnings({ "unchecked" }) // Unchecked casts to LiteralExpression
+    private <T> PatchedWhereExpressionImpl getBinaryComparisonExpression(Expression a, BinaryComparisonOperator op, Expression b) {
+        LiteralExpression<Key <T>> leftOperand = (LiteralExpression<Key<T>>)a;
+        LiteralExpression<T> rightOperand = (LiteralExpression<T>)b;
+        BinaryComparisonExpression<T> impl = new BinaryComparisonExpression<>(
+                leftOperand, op, rightOperand);
+        return new PatchedWhereExpressionImpl(impl);
+    }
+
+    @Override
+    public void print(int level) {
+        for (int i = 0; i < level; i++) {
+            System.out.print("-");
+        }
+        System.out.print("B: " + getOperator());
+        System.out.println("");
+        int newLevel = level + 1;
+        if (leftChild != null) {
+            leftChild.print(newLevel);
+        }
+        if (rightChild != null) {
+            rightChild.print(newLevel);
+        }
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        if (other == null) {
+            return false;
+        }
+        if (!(other instanceof BinaryExpressionNode)) {
+            return false;
+        }
+        BinaryExpressionNode o = (BinaryExpressionNode)other;
+        return this.getOperator().equals(o.getOperator()) &&
+                Objects.equals(leftChild, o.leftChild) &&
+                Objects.equals(rightChild, o.rightChild);
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(getOperator(), leftChild, rightChild, getParent());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/IllegalPatchException.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * Thrown if a prepared statement was attempted to patch, which resulted in
+ * NPEs or a type mismatch for some of the free variables.
+ *
+ */
+@SuppressWarnings("serial")
+class IllegalPatchException extends Exception {
+
+    IllegalPatchException(Throwable cause) {
+        super(cause);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/LimitExpression.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+/**
+ * Represents a limit expression in the prepared statement's parse tree.
+ *
+ */
+class LimitExpression implements Printable, Patchable {
+
+    private Object value;
+
+    public Object getValue() {
+        return value;
+    }
+
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    @Override
+    public void print(int level) {
+        System.out.println("LIMIT: " + getValue());
+    }
+
+    @Override
+    public PatchedLimitExpression patch(PreparedParameter[] params)
+            throws IllegalPatchException {
+        if (value instanceof Unfinished) {
+            Unfinished unfinished = (Unfinished)value;
+            try {
+                PreparedParameter param = params[unfinished.getParameterIndex()];
+                Class<?> typeClass = param.getType();
+                if (typeClass != Integer.class) {
+                    String msg = "Invalid parameter type for limit expression. Expected integer!";
+                    IllegalArgumentException e = new IllegalArgumentException(msg);
+                    throw e;
+                }
+                int limitVal = (Integer)param.getValue();
+                return new PatchedLimitExpressionImpl(limitVal);
+            } catch (Exception e) {
+                throw new IllegalPatchException(e);
+            }
+        } else {
+            // must have been int, since parsing would have failed otherwise
+            int limitVal = (int)getValue();
+            return new PatchedLimitExpressionImpl(limitVal);
+        }
+    }
+    
+    private static class PatchedLimitExpressionImpl implements PatchedLimitExpression {
+        
+        private final int val;
+        
+        PatchedLimitExpressionImpl(int limitVal) {
+            this.val = limitVal;
+        }
+
+        @Override
+        public int getLimitValue() {
+            return val;
+        }
+        
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Node.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,118 @@
+/*
+ * 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.Objects;
+
+/**
+ * A basic node in the prepared statement parse tree.
+ *
+ */
+class Node implements Printable, Patchable {
+
+    private Node parent;
+
+    private Object value;
+    
+    Node(Node parent) {
+        this.parent = parent;
+    }
+    
+    public Object getValue() {
+        return value;
+    }
+
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    public Node getParent() {
+        return parent;
+    }
+
+    public void setParent(Node parent) {
+        this.parent = parent;
+    }
+
+    @Override
+    public void print(int level) {
+        for (int i = 0; i < level; i++) {
+            System.out.print("-");
+        }
+        System.out.print(getValue());
+        System.out.println("");
+        if (value instanceof Node) {
+            Node node = (Node)value;
+            node.print(level++);
+        }
+    }
+    
+    @Override
+    public PatchedWhereExpression patch(PreparedParameter[] params) throws IllegalPatchException {
+        if (getValue() == null || !(getValue() instanceof Node) ) {
+            String msg = Node.class.getSimpleName() +
+                    " invalid when attempted to patch";
+            IllegalStateException cause = new IllegalStateException(msg);
+            throw new IllegalPatchException(cause);
+        }
+        Node node = (Node)getValue();
+        return node.patch(params);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (other == null) {
+            return false;
+        }
+        if (!(other instanceof Node)) {
+            return false;
+        }
+        Node o = (Node)other;
+        return Objects.equals(getValue(), o.getValue());
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(getValue());
+    }
+    
+    @Override
+    public String toString() {
+        return "Node: " + getValue();
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/NotBooleanExpressionNode.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,100 @@
+/*
+ * 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.Objects;
+
+import com.redhat.thermostat.storage.query.ComparisonExpression;
+import com.redhat.thermostat.storage.query.UnaryLogicalExpression;
+import com.redhat.thermostat.storage.query.UnaryLogicalOperator;
+
+/**
+ * A node representing a boolean not expression in the prepared statement's
+ * parse tree.
+ *
+ */
+class NotBooleanExpressionNode extends UnaryExpressionNode {
+
+    NotBooleanExpressionNode(Node parent) {
+        super(parent);
+    }
+
+    @Override
+    public UnaryLogicalOperator getOperator() {
+        return UnaryLogicalOperator.NOT;
+    }
+    
+    @Override
+    public PatchedWhereExpression patch(PreparedParameter[] params) throws IllegalPatchException {
+        if (getValue() == null || !(getValue() instanceof Node) ) {
+            String msg = getClass().getSimpleName() +
+                    " invalid when attempted to patch";
+            IllegalStateException cause = new IllegalStateException(msg);
+            throw new IllegalPatchException(cause);
+        }
+        Node node = (Node)getValue();
+        PatchedWhereExpression patched = node.patch(params);
+        // If this cast fails we are in serious trouble. Mongodb doesn't support
+        // something like NOT ( a AND b ). However, the grammar does not support
+        // parenthesized expressions, NOT has higher precedence as AND/OR
+        // expressions and the LHS and RHS of binary boolean expressions are
+        // required to be binary comparison expressions.
+        // Hence, we wouldn't parse an expression such as the above anyway.
+        ComparisonExpression expr = (ComparisonExpression)patched.getExpression();
+        UnaryLogicalExpression<ComparisonExpression> notExpr = new UnaryLogicalExpression<>(
+                expr, getOperator());
+        return new PatchedWhereExpressionImpl(notExpr);
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        if (other == null) {
+            return false;
+        }
+        if (!(other instanceof NotBooleanExpressionNode)) {
+            return false;
+        }
+        NotBooleanExpressionNode o = (NotBooleanExpressionNode)other;
+        return Objects.equals(getValue(), o.getValue());
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(getOperator(), getValue());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Operator.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+/**
+ * Valid operators in the string representation of a prepared statement
+ * descriptor.
+ *
+ */
+enum Operator {
+    
+    /** Logical AND operation */
+    AND("AND"),
+    /** Logical OR operation */
+    OR("OR"),
+    /** Logical NOT operation */
+    NOT("NOT"),
+    /** Equality comparison */
+    EQUALS("="),
+    /** Inequality comparison */
+    NOT_EQUAL_TO("!="),
+    /** Greater than comparison */
+    GREATER_THAN(">"),
+    /** Greater than or equal comparison */
+    GREATER_THAN_OR_EQUAL_TO(">="),
+    /** Less than comparison */
+    LESS_THAN("<"),
+    /** Less than or equal comparison */
+    LESS_THAN_OR_EQUAL_TO("<=");
+    
+    private String name;
+    
+    Operator(String name) {
+        this.name = name;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatement.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,155 @@
+/*
+ * 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 com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Statement;
+import com.redhat.thermostat.storage.query.Expression;
+
+/**
+ * Result object as returned by {@link StatementDescriptorParser#parse()}.
+ * An instance of this plus an {@link PreparedStatementImpl} instance should
+ * be sufficient to patch up a prepared statement with its real values.
+ *
+ * @see PreparedStatementImpl#executeQuery()
+ */
+class ParsedStatement {
+
+    private int numParams;
+    private SuffixExpression suffixExpn;
+    private final Statement statement;
+    
+    ParsedStatement(Statement statement) {
+        this.statement = statement;
+    }
+    
+    public int getNumParams() {
+        return numParams;
+    }
+
+    public Statement getRawStatement() {
+        return statement;
+    }
+    
+    public Statement patchQuery(PreparedStatementImpl stmt) throws IllegalPatchException {
+        if (suffixExpn == null) {
+            String msg = "Suffix expression must be set before patching!";
+            IllegalStateException expn = new IllegalStateException(msg);
+            throw new IllegalPatchException(expn);
+        }
+        PreparedParameter[] params = stmt.getParams();
+        patchWhere(params);
+        patchSort(params);
+        patchLimit(params);
+        // TODO count actual patches and throw an exception if not all vars
+        // have been patched up.
+        return statement;
+    }
+
+    private void patchLimit(PreparedParameter[] params) throws IllegalPatchException {
+        LimitExpression expn = suffixExpn.getLimitExpn();
+        if (expn == null) {
+            // no limit expn, nothing to do
+            return;
+        }
+        PatchedLimitExpression patchedExp = expn.patch(params);
+        if (statement instanceof Query<?>) {
+            Query<?> query = (Query<?>)statement;
+            query.limit(patchedExp.getLimitValue());
+        } else {
+            String msg = "Patching of non-query types not (yet) supported! Class was:"
+                    + statement.getClass().getName();
+            IllegalStateException invalid = new IllegalStateException(msg);
+            throw new IllegalPatchException(invalid);
+        }
+    }
+
+    private void patchSort(PreparedParameter[] params) throws IllegalPatchException {
+        SortExpression expn = suffixExpn.getSortExpn();
+        if (expn == null) {
+            // no sort expn, nothing to do
+            return;
+        }
+        PatchedSortExpression patchedExp = expn.patch(params);
+        if (statement instanceof Query<?>) {
+            Query<?> query = (Query<?>)statement;
+            PatchedSortMember[] members = patchedExp.getSortMembers();
+            for (int i = 0; i < members.length; i++) {
+                query.sort(members[i].getSortKey(), members[i].getDirection());
+            }
+        } else {
+            String msg = "Patching of non-query types not (yet) supported! Class was:"
+                    + statement.getClass().getName();
+            IllegalStateException invalid = new IllegalStateException(msg);
+            throw new IllegalPatchException(invalid);
+        }
+    }
+
+    private void patchWhere(PreparedParameter[] params) throws IllegalPatchException {
+        WhereExpression expn = suffixExpn.getWhereExpn();
+        if (expn == null) {
+            // no where, nothing to do
+            return;
+        }
+        // walk the tree, create actual expressions and patch values along
+        // the way.
+        PatchedWhereExpression patchedExp = expn.patch(params);
+        Expression whereClause = patchedExp.getExpression();
+        if (statement instanceof Query<?>) {
+            Query<?> query = (Query<?>)statement;
+            query.where(whereClause);
+        } else {
+            String msg = "Patching of non-query types not (yet) supported! Class was:"
+                    + statement.getClass().getName();
+            IllegalStateException invalid = new IllegalStateException(msg);
+            throw new IllegalPatchException(invalid);
+        }
+    }
+
+    public void setNumFreeParams(int num) {
+        this.numParams = num;
+    }
+
+    public void setSuffixExpression(SuffixExpression tree) {
+        this.suffixExpn = tree;
+    }
+
+    public SuffixExpression getSuffixExpression() {
+        return suffixExpn;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Patchable.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+/**
+ * Interface for patchable objects in a ParsedStatement.
+ *
+ */
+interface Patchable {
+    
+    /**
+     * 
+     * @param params The parameters which should be used for patching.
+     * @return The finished (a.k.a patched) expression.
+     * @throws IllegalPatchException If something failed during patching.
+     */
+    PatchedExpression patch(PreparedParameter[] params) throws IllegalPatchException;
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedExpression.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,99 @@
+/*
+ * 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 com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+import com.redhat.thermostat.storage.core.Key;
+
+/**
+ * Return type for patched expressions
+ * 
+ * @see Patchable
+ */
+interface PatchedExpression {
+    // marker interface
+}
+
+interface PatchedWhereExpression extends PatchedExpression {
+    
+    /**
+     * @return The patched where expression.
+     * 
+     * @see {@link Expression}
+     */
+    Expression getExpression();
+}
+
+interface PatchedSortExpression extends PatchedExpression {
+    
+    /**
+     * 
+     * @return The patched sorts.
+     * 
+     * @see {@link PatchedSortMember}
+     * @see {@link Query#sort(Key, SortDirection)}
+     */
+    PatchedSortMember[] getSortMembers();
+    
+}
+
+interface PatchedSortMemberExpression extends PatchedExpression {
+    
+    /**
+     * 
+     * @return The patched sorts.
+     * 
+     * @see {@link PatchedSortMember}
+     * @see {@link Query#sort(Key, SortDirection)}
+     */
+    PatchedSortMember getSortMember();
+    
+}
+
+interface PatchedLimitExpression extends PatchedExpression {
+    
+    /**
+     * 
+     * @return The patched limit value.
+     * 
+     * @see {@link Query#limit(int)}
+     */
+    int getLimitValue();
+    
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedSortMember.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,66 @@
+/*
+ * 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 com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+
+/**
+ * Data structure representing patched sort members.
+ *
+ * @see Patchable
+ * @see PatchedSortExpression
+ * @see SortMember
+ */
+class PatchedSortMember {
+
+    private final SortDirection direction;
+    private final Key<?> sortKey;
+
+    PatchedSortMember(Key<?> sortKey, SortDirection direction) {
+        this.direction = direction;
+        this.sortKey = sortKey;
+    }
+    
+    SortDirection getDirection() {
+        return direction;
+    }
+
+    Key<?> getSortKey() {
+        return sortKey;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedWhereExpressionImpl.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,57 @@
+/*
+ * 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 com.redhat.thermostat.storage.query.Expression;
+
+/**
+ * Implementation of the return type of patchable where expressions.
+ *
+ */
+class PatchedWhereExpressionImpl implements PatchedWhereExpression {
+    
+    private final Expression expn;
+    
+    PatchedWhereExpressionImpl(Expression expn) {
+        this.expn = expn;
+    }
+
+    @Override
+    public Expression getExpression() {
+        return expn;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedParameter.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * Represents a prepared parameter.
+ *
+ * @see PreparedStatementImpl
+ */
+class PreparedParameter {
+
+    private final Object value;
+    private final Class<?> type;
+    
+    PreparedParameter(Object value, Class<?> type) {
+        this.value = value;
+        this.type = type;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    public Class<?> getType() {
+        return type;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,157 @@
+/*
+ * 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 com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DataModifyingStatement;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Statement;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.StatementExecutionException;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.model.Pojo;
+
+/**
+ * Main implementation of {@link PreparedStatement}s.
+ *
+ */
+final public class PreparedStatementImpl implements PreparedStatement {
+    
+    private Query<? extends Pojo> query;
+    private DataModifyingStatement dmlStatement;
+    private final PreparedParameter[] params;
+    private final ParsedStatement parsedStatement;
+    
+    public PreparedStatementImpl(Storage storage, StatementDescriptor desc) throws DescriptorParsingException {
+        StatementDescriptorParser parser = new StatementDescriptorParser(storage, desc);
+        this.parsedStatement = parser.parse();
+        int numParams = parsedStatement.getNumParams();
+        params = new PreparedParameter[numParams];
+        Statement statement = parsedStatement.getRawStatement();
+        if (statement instanceof DataModifyingStatement) {
+            this.dmlStatement = (DataModifyingStatement)statement;
+        } else if (statement instanceof Query<?>) {
+            this.query = (Query<?>)statement;
+        }
+    }
+    
+    // used for testing ParsedStatements
+    PreparedStatementImpl(int numParams) {
+        params = new PreparedParameter[numParams];
+        this.parsedStatement = null;
+    }
+    
+    @Override
+    public void setLong(int paramIndex, long paramValue) {
+        setType(paramIndex, paramValue, Long.class);
+    }
+
+    @Override
+    public void setInt(int paramIndex, int paramValue) {
+        setType(paramIndex, paramValue, Integer.class);
+    }
+
+    @Override
+    public void setStringList(int paramIndex, String[] paramValue) {
+        setType(paramIndex, paramValue, String[].class);
+    }
+    
+    @Override
+    public void setBoolean(int paramIndex, boolean paramValue) {
+        setType(paramIndex, paramValue, boolean.class);
+    }
+
+    private void setType(int paramIndex, Object paramValue, Class<?> paramType) {
+        if (paramIndex >= params.length) {
+            throw new IllegalArgumentException("Parameter index '" + paramIndex + "' out of range.");
+        }
+        PreparedParameter param = new PreparedParameter(paramValue, paramType);
+        params[paramIndex] = param;
+    }
+
+    @Override
+    public int execute() throws StatementExecutionException {
+        if (dmlStatement == null) {
+            throw new IllegalStateException(
+                    "Can't execute statement which isn't an instance of "
+                            + DataModifyingStatement.class.getName());
+        }
+        try {
+            dmlStatement = (DataModifyingStatement)parsedStatement.patchQuery(this);
+        } catch (Exception e) {
+            throw new StatementExecutionException(e);
+        }
+        return dmlStatement.execute();
+    }
+
+    // unchecked since query has no knowledge of type
+    @SuppressWarnings("unchecked")
+    @Override
+    public Cursor<?> executeQuery() throws StatementExecutionException{
+        if (query == null) {
+            throw new IllegalStateException(
+                    "Can't execute statement which isn't an instance of "
+                            + Query.class.getName());
+        }
+        try {
+            // FIXME: I'm sure we can improve on this. We should avoid walking the
+            // tree each time. Some cache with unfinished nodes and a reference
+            // to the matching expression should be sufficient.
+            query = (Query<?>)parsedStatement.patchQuery(this);
+        } catch (IllegalPatchException e) {
+            throw new StatementExecutionException(e);
+        }
+        return query.execute();
+    }
+
+    @Override
+    public int getId() {
+        // not implemented for Mongo
+        return -1;
+    }
+
+    @Override
+    public void setString(int paramIndex, String paramValue) {
+        setType(paramIndex, paramValue, String.class);
+    }
+
+    PreparedParameter[] getParams() {
+        return params;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Printable.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,60 @@
+/*
+ * 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 com.redhat.thermostat.storage.core.PreparedStatement;
+
+/**
+ * Implement this interface for printable nodes in the
+ * {@link PreparedStatement} parse tree. This is mainly useful for debugging
+ * purposes. Most nodes of the prepared statement's parse tree implement it.
+ * 
+ *
+ * @see SuffixExpression#printExpn();
+ * @see WhereExpression#print(int);
+ * @see SortExpression#print(int);
+ * @see LimitExpression#print(int);
+ */
+interface Printable {
+
+    /**
+     * Print a {@link Node} in a tree-like fashion.
+     * 
+     * @param level The level of this node in the tree.
+     */
+    void print(int level);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortExpression.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+/**
+ * Represents a sort expression of a prepared statement's parse tree.
+ *
+ */
+class SortExpression implements Printable, Patchable {
+    
+    List<SortMember> members = new ArrayList<>();
+    
+    public void addMember(SortMember member) {
+        members.add(member);
+    }
+    
+    public List<SortMember> getMembers() {
+        return members;
+    }
+
+    @Override
+    public void print(int level) {
+        System.out.print("SORT: ");
+        for (int i = 0; i < members.size(); i++) {
+            SortMember member = members.get(i);
+            member.print(level);
+            if (i < members.size() - 1) {
+                System.out.print(", ");
+            }
+        }
+        System.out.println("");
+    }
+
+    @Override
+    public PatchedSortExpression patch(PreparedParameter[] params)
+            throws IllegalPatchException {
+        List<PatchedSortMember> patchedMembers = new ArrayList<>();
+        for (SortMember member: members) {
+            PatchedSortMemberExpression expn = member.patch(params);
+            patchedMembers.add(expn.getSortMember());
+        }
+        // worked so far (no exceptions) create impl exp and return
+        PatchedSortMember[] members = patchedMembers.toArray(new PatchedSortMember[0]);
+        return new PatchedSortExpressionImpl(members);
+    }
+
+    private static class PatchedSortExpressionImpl implements PatchedSortExpression {
+
+        private final PatchedSortMember[] members;
+        
+        private PatchedSortExpressionImpl(PatchedSortMember[] members) {
+            this.members = members;
+        }
+        
+        @Override
+        public PatchedSortMember[] getSortMembers() {
+            return members;
+        }
+        
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SortMember.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,115 @@
+/*
+ * 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 com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+
+/**
+ * Represents sort members.
+ * 
+ * @see SortExpression
+ *
+ */
+class SortMember implements Printable, Patchable {
+
+    private SortDirection direction;
+    private Object sortKey;
+
+    public SortDirection getDirection() {
+        return direction;
+    }
+
+    public void setDirection(SortDirection direction) {
+        this.direction = direction;
+    }
+
+    public Object getSortKey() {
+        return sortKey;
+    }
+
+    public void setSortKey(Object sortKey) {
+        this.sortKey = sortKey;
+    }
+
+    @Override
+    public void print(int level) {
+        System.out.print(getSortKey() + " " + getDirection().name());
+    }
+
+    @Override
+    public PatchedSortMemberExpression patch(PreparedParameter[] params)
+            throws IllegalPatchException {
+        try {
+            String keyVal = null;
+            if (getSortKey() instanceof Unfinished) {
+                Unfinished unfinished = (Unfinished)getSortKey();
+                PreparedParameter p = params[unfinished.getParameterIndex()];
+                if (p.getType() != String.class) {
+                    String msg = "Illegal parameter type for index "
+                            + unfinished.getParameterIndex()
+                            + ". Expected String!";
+                    IllegalArgumentException iae = new IllegalArgumentException(msg);
+                    throw iae;
+                }
+                keyVal = (String)p.getValue();
+            } else {
+                keyVal = (String)getSortKey();
+            }
+            Key<?> sortKey = new Key<>(keyVal, false);
+            PatchedSortMember m = new PatchedSortMember(sortKey, getDirection());
+            return new PatchedSortMemberExpressionImpl(m);
+        } catch (Exception e) {
+            throw new IllegalPatchException(e);
+        }
+    }
+    
+    private static class PatchedSortMemberExpressionImpl implements PatchedSortMemberExpression {
+        
+        private final PatchedSortMember member;
+        private PatchedSortMemberExpressionImpl(PatchedSortMember member) {
+            this.member = member;
+        }
+        
+        @Override
+        public PatchedSortMember getSortMember() {
+            return member;
+        }
+        
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,561 @@
+/*
+ * 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.Category;
+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.StatementDescriptor;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+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
+ * (currently it only supports QUERY statement types):
+ * 
+ * <pre>
+ * statementDesc := statementType category suffix
+ * statementType := 'QUERY'
+ * category      := literal
+ * suffix        := 'WHERE' where |
+ *                  'SORT' sortCond |
+ *                  'LIMIT' term | \empty
+ * where         := whereExp sort limit
+ * whereExp      := andCond orCond
+ * orCond        := 'OR' andCond | \empty
+ * sort          := 'SORT' sortCond | \empty
+ * sortCond      := sortPair sortList
+ * sortPair      := literal sortModifier
+ * sortModifier  := 'ASC' | 'DSC'
+ * sortList      := ',' sortCond | \empty
+ * limit         := 'LIMIT' term | \empty
+ * andCond       := condition andBody
+ * andBody       := 'AND' condition | \empty
+ * condition     := 'NOT' condition | compExp
+ * compExp       := term compExpRHS
+ * term          := freeParam | literal
+ * freeParam     := '?s' | '?i' | '?s[' | '?b'
+ * literal       := sQuote string sQuote | int | boolean
+ * sQuote        := \'
+ * boolean       := &lt;true&gt; | &lt;false&gt;
+ * int           := &lt;literal-int&gt;
+ * string        := &lt;literal-string-value&gt;
+ * compExpRHS    := '=' 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 {
+
+    private static final String TOKEN_DELIMS = " \t\r\n\f";
+    private static final String[] KNOWN_STATEMENT_TYPES = new String[] {
+        "QUERY",
+    };
+    private static final String SORTLIST_SEP = ",";
+    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 char PARAM_PLACEHOLDER = '?';
+    
+    private final String[] tokens;
+    private final StatementDescriptor desc;
+    private final Storage storage;
+    private int currTokenIndex;
+    private int placeHolderCount;
+    // the parsed statement
+    private ParsedStatement parsedStatement;
+    private SuffixExpression tree;
+    
+    StatementDescriptorParser(Storage storage, StatementDescriptor desc) {
+        this.tokens = getTokens(desc.getQueryDescriptor());
+        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 parse() throws DescriptorParsingException {
+        matchStatementType();
+        matchCategory();
+        // matched so far, create the raw statement
+        createStatement();
+        this.tree = new SuffixExpression();
+        matchSuffix();
+        if (currTokenIndex != tokens.length) {
+            throw new DescriptorParsingException("Incomplete parse");
+        }
+        parsedStatement.setNumFreeParams(placeHolderCount);
+        parsedStatement.setSuffixExpression(tree);
+        return parsedStatement;
+    }
+
+    /*
+     * 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);
+            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(WhereExpression expn) throws DescriptorParsingException {
+        if (currTokenIndex >= tokens.length) {
+            throw new DescriptorParsingException("Illegal where clause");
+        }
+        matchAndCondition(expn.getRoot());
+        matchOrCondition(expn.getRoot());
+    }
+
+    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.setRightChild(expr);
+            } else {
+                currNodeExpr.setLeftChild(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);
+            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);
+    }
+    
+    private void matchTerm(LimitExpression expn) throws DescriptorParsingException {
+        String term = getTerm();
+        if (term.charAt(0) == PARAM_PLACEHOLDER) {
+            assert(placeHolderCount > 0);
+            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);
+    }
+
+    private void matchTerm(TerminalNode node, boolean isLHS) throws DescriptorParsingException {
+        String term = getTerm();
+        if (term.charAt(0) == PARAM_PLACEHOLDER) {
+            assert(placeHolderCount > 0);
+            UnfinishedValueNode patchNode = new UnfinishedValueNode();
+            patchNode.setParameterIndex(placeHolderCount - 1);
+            patchNode.setLHS(isLHS);
+            // figure out the expected type
+            Class<?> expectedType = getType(term.substring(1));
+            if (expectedType == null) {
+                throw new DescriptorParsingException("Unknown type of free parameter: '" + term + "'");
+            }
+            patchNode.setType(expectedType);
+            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, false);
+            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 int or boolean
+            try {
+                int intVal = Integer.parseInt(term);
+                return intVal;
+            } catch (NumberFormatException nfe) {
+                if (term.equals(Boolean.toString(false)) || term.equals(Boolean.toString(true))) {
+                    boolean boolVal = Boolean.parseBoolean(term);
+                    return boolVal;
+                } else {
+                    throw new DescriptorParsingException("Illegal terminal type. Token was ->" + term + "<-");
+                }
+            }
+        }
+    }
+
+    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 Class<?> getType(String term) {
+        if (term.equals("")) {
+            // illegal type
+            return null;
+        }
+        assert(term.equals("i") || term.equals("s") || term.equals("s[") || term.equals("b"));
+        char switchChar = term.charAt(0);
+        Class<?> type = null;
+        switch (switchChar) {
+        case 'i': {
+            type = Integer.class;
+            break;
+        }
+        case 's': {
+            if (term.length() == 1) {
+                type = String.class;
+            } else if (term.length() == 2 && term.charAt(1) == '[') {
+                type = String[].class;
+            }
+            break;
+        }
+        case 'b': {
+            type = boolean.class;
+            break;
+        }
+        default:
+            assert (type == null);
+            break;
+        }
+        return type;
+    }
+
+    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.getParent();
+            BinaryExpressionNode and = new BinaryExpressionNode(parent);
+            and.setLeftChild((Node)currNode.getValue());
+            currNode.setParent(and);
+            and.setOperator(BinaryLogicalOperator.AND);
+            currNode.setValue(and);
+            
+            matchCondition(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.getParent();
+            BinaryExpressionNode or = new BinaryExpressionNode(parent);
+            or.setLeftChild((Node)currNode.getValue());
+            currNode.setParent(or);
+            or.setOperator(BinaryLogicalOperator.OR);
+            currNode.setValue(or);
+            
+            matchAndCondition(or);
+        }
+        // empty
+    }
+
+    private void createStatement() {
+        if (tokens[0].equals(KNOWN_STATEMENT_TYPES[0])) {
+            // query case
+            Query<?> query = storage.createQuery(desc.getCategory());
+            this.parsedStatement = new ParsedStatement(query);
+        } else {
+            throw new IllegalStateException("Don't know how to create statement type '" + tokens[0] + "'");
+        }
+    }
+
+    private void matchCategory() throws DescriptorParsingException {
+        if (currTokenIndex >= tokens.length) {
+            throw new DescriptorParsingException("Missing category name in descriptor: '" + desc.getQueryDescriptor() + "'");
+        }
+        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 {
+        // matches 'QUERY' only at this point
+        if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[0])) {
+            currTokenIndex++;
+        } else {
+            throw new DescriptorParsingException("Unknown statement type: '" + tokens[currTokenIndex] + "'");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SuffixExpression.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+/**
+ * Container for where, sort and limit expressions.
+ *
+ */
+class SuffixExpression {
+
+    private WhereExpression whereExpn;
+    private SortExpression sortExpn;
+    private LimitExpression limitExpn;
+
+    public WhereExpression getWhereExpn() {
+        return whereExpn;
+    }
+
+    public void setWhereExpn(WhereExpression whereExpn) {
+        this.whereExpn = whereExpn;
+    }
+
+    public SortExpression getSortExpn() {
+        return sortExpn;
+    }
+
+    public void setSortExpn(SortExpression sortExpn) {
+        this.sortExpn = sortExpn;
+    }
+
+    public LimitExpression getLimitExpn() {
+        return limitExpn;
+    }
+
+    public void setLimitExpn(LimitExpression limitExpn) {
+        this.limitExpn = limitExpn;
+    }
+
+    /**
+     * Prints the entire suffix expression to stdout.
+     */
+    public void printExpn() {
+        if (whereExpn != null) {
+            whereExpn.print(0);
+        }
+        if (sortExpn != null) {
+            sortExpn.print(0);
+        }
+        if (limitExpn != null) {
+            limitExpn.print(0);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/TerminalNode.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,110 @@
+/*
+ * 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.Objects;
+
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.query.LiteralExpression;
+
+/**
+ * Node representing a leaf node in the prepared statement parse tree.
+ *
+ */
+class TerminalNode extends Node {
+
+    TerminalNode(Node parent) {
+        super(parent);
+    }
+    
+    @Override
+    public PatchedWhereExpression patch(PreparedParameter[] params) throws IllegalPatchException {
+        if (getValue() == null) {
+            String msg = TerminalNode.class.getSimpleName() +
+                    " invalid when attempted to patch";
+            IllegalStateException cause = new IllegalStateException(msg);
+            throw new IllegalPatchException(cause);
+        }
+        Object actualValue;
+        if (getValue() instanceof UnfinishedValueNode) {
+            // need to patch the value
+            UnfinishedValueNode patch = (UnfinishedValueNode)getValue();
+            PreparedParameter param = null;
+            try {
+                param = params[patch.getParameterIndex()];
+            } catch (Exception e) {
+                throw new IllegalPatchException(e);
+            }
+            if (param.getType() != patch.getType()) {
+                String msg = TerminalNode.class.getSimpleName()
+                        + " invalid type when attempting to patch. Expected "
+                        + patch.getType().getName() + " but was "
+                        + param.getType().getName();
+                IllegalArgumentException iae = new IllegalArgumentException(msg);
+                throw new IllegalPatchException(iae);
+            }
+            if (patch.isLHS()) {
+                // LHS need to get patched to keys
+                Key<?> valueKey = new Key<>((String)param.getValue(), false);
+                actualValue = valueKey;
+            } else {
+                actualValue = param.getValue();
+            }
+        } else {
+            actualValue = getValue();
+        }
+        LiteralExpression<?> literalExp = new LiteralExpression<>(actualValue);
+        return new PatchedWhereExpressionImpl(literalExp);
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        if (other == null) {
+            return false;
+        }
+        if (!(other instanceof TerminalNode)) {
+            return false;
+        }
+        TerminalNode o = (TerminalNode)other;
+        return Objects.equals(getValue(), o.getValue());
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(getValue());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnaryExpressionNode.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+/**
+ * @see NotBooleanExpressionNode
+ *
+ */
+abstract class UnaryExpressionNode extends Node {
+
+    UnaryExpressionNode(Node parent) {
+        super(parent);
+    }
+    
+    public abstract Object getOperator();
+    
+    @Override
+    public void print(int level) {
+        Node value = (Node)getValue();
+        for (int i = 0; i < level; i++) {
+            System.out.print("-");
+        }
+        System.out.print("U: " + getOperator());
+        System.out.println("");
+        value.print(level++);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/Unfinished.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+/**
+ * Marker interface for unfinished nodes in a prepared statement's parse tree.
+ *
+ */
+public interface Unfinished {
+
+    /**
+     * @return The index which contains the actual value of this unfinished
+     *         node.
+     */
+    int getParameterIndex();
+
+    /**
+     * Sets the index which should be used for indexing into the list of
+     * parameters which contains actual values.
+     * 
+     * @param parameterIndex
+     */
+    void setParameterIndex(int parameterIndex);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedLimitValue.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+/**
+ * 
+ * Represents an {@link Unfinished} limit value.
+ * 
+ * @see Patchable
+ *
+ */
+class UnfinishedLimitValue extends AbstractUnfinished {
+
+    private int parameterIndex = -1;
+
+    @Override
+    public int getParameterIndex() {
+        return parameterIndex;
+    }
+
+    @Override
+    public void setParameterIndex(int parameterIndex) {
+        this.parameterIndex = parameterIndex;
+    }
+    
+    @Override
+    public String toString() {
+        return "Unfinished limit value (" + getParameterIndex() + ")";
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedSortKey.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+/**
+ * 
+ * Represents an {@link Unfinished} sort value.
+ * 
+ * @see Patchable
+ *
+ */
+class UnfinishedSortKey extends AbstractUnfinished {
+
+    private int paramIndex = -1;
+    
+    @Override
+    public int getParameterIndex() {
+        return paramIndex;
+    }
+
+    @Override
+    public void setParameterIndex(int parameterIndex) {
+        this.paramIndex = parameterIndex;
+    }
+    
+    @Override
+    public String toString() {
+        return "Unfinished sort key (" + getParameterIndex() + ")";
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,108 @@
+/*
+ * 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.Objects;
+
+/**
+ * 
+ * Represents an {@link Unfinished} node in the where expressions parse tree
+ * of prepared statements.
+ * 
+ * @see Patchable
+ *
+ */
+class UnfinishedValueNode extends AbstractUnfinished {
+
+    private int parameterIndex = -1;
+    // determines if this patched value is a LHS or if false a RHS of a
+    // binary comparison.
+    private boolean isLHS;
+    // Specifies the expected type of this free parameter.
+    private Class<?> type;
+
+    Class<?> getType() {
+        return type;
+    }
+
+    void setType(Class<?> type) {
+        this.type = type;
+    }
+
+    boolean isLHS() {
+        return isLHS;
+    }
+
+    void setLHS(boolean isLHS) {
+        this.isLHS = isLHS;
+    }
+
+    @Override
+    public int getParameterIndex() {
+        return parameterIndex;
+    }
+
+    @Override
+    public void setParameterIndex(int parameterIndex) {
+        this.parameterIndex = parameterIndex;
+    }
+    
+    @Override
+    public String toString() {
+        return "Unfinished value (" + getParameterIndex() + ")";
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        boolean basics = super.equals(other);
+        if (!basics) {
+            return false;
+        }
+        if (!(other instanceof UnfinishedValueNode)) {
+            return false;
+        }
+        UnfinishedValueNode o = (UnfinishedValueNode)other;
+        return basics && Objects.equals(isLHS(), o.isLHS) &&
+                Objects.equals(getType(), o.getType());
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(getParameterIndex(), isLHS(), getType());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/WhereExpression.java	Fri Jun 28 15:27:50 2013 +0200
@@ -0,0 +1,72 @@
+/*
+ * 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;
+
+/**
+ * Data structure representing a where expression.
+ *
+ * @see Node
+ * @see SuffixExpression
+ * @see StatementDescriptorParser
+ */
+class WhereExpression implements Printable, Patchable {
+
+    private final Node root;
+    
+    WhereExpression() {
+        this.root = new Node(null);
+    }
+
+    public Node getRoot() {
+        return root;
+    }
+
+    @Override
+    public void print(int level) {
+        System.out.println("WHERE:");
+        Node node = (Node)root.getValue();
+        node.print(0);
+    }
+
+    @Override
+    public PatchedWhereExpression patch(PreparedParameter[] params)
+            throws IllegalPatchException {
+        Node node = (Node)root.getValue();
+        return node.patch(params);
+    }
+    
+}
--- a/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java	Wed Jul 03 12:12:47 2013 -0600
+++ b/storage/mongo/src/main/java/com/redhat/thermostat/storage/mongodb/internal/MongoStorage.java	Fri Jun 28 15:27:50 2013 +0200
@@ -61,10 +61,14 @@
 import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
 import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
 import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.PreparedStatementFactory;
 import com.redhat.thermostat.storage.core.Query;
 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.Storage;
 import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.Pojo;
@@ -332,5 +336,14 @@
         }
     }
 
+    @Override
+    public PreparedStatement prepareStatement(StatementDescriptor statementDesc)
+            throws DescriptorParsingException {
+        // FIXME: Use some kind of cache in order to avoid parsing of
+        // descriptors each time this is called. At least if the descriptor
+        // class is the same we should be able to do something here.
+        return PreparedStatementFactory.getInstance(this, statementDesc);
+    }
+
 }
 
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Wed Jul 03 12:12:47 2013 -0600
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Fri Jun 28 15:27:50 2013 +0200
@@ -95,11 +95,15 @@
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Connection;
 import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedStatement;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Remove;
 import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.SecureStorage;
+import com.redhat.thermostat.storage.core.Statement;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
 import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.StorageException;
 import com.redhat.thermostat.storage.core.Update;
@@ -703,5 +707,12 @@
         return categoryIds.get(category);
     }
 
+    @Override
+    public PreparedStatement prepareStatement(StatementDescriptor desc)
+            throws DescriptorParsingException {
+        // TODO Implement
+        return null;
+    }
+
 }