changeset 1263:ac5c298a9dae

Implement support for prepared writes. Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-September/008174.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Tue, 03 Sep 2013 14:19:10 +0200
parents 16c666300582
children e103fd511693
files main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties storage/core/pom.xml storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementDescriptor.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/ParsedStatementImpl.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/PatchedSetListMember.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverter.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/SetList.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SetListValue.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/UnfinishedValueNode.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverterTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/SetListValueTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java storage/mongo/pom.xml web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java
diffstat 22 files changed, 1843 insertions(+), 86 deletions(-) [+]
line wrap: on
line diff
--- a/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties	Mon Sep 30 16:38:50 2013 +0200
+++ b/main/src/main/resources/com/redhat/thermostat/main/impl/bootstrapbundles.properties	Tue Sep 03 14:19:10 2013 +0200
@@ -1,5 +1,14 @@
+# FIXME: Remove commons-beanutils, commons-codec, commons-logging,
+#        commons-collections from bootstrap list once storage-core
+#        does no longer depend on beanutils. It has been made
+#        dependent on this because it was required as an interim
+#        measure to switch over to prepared writes.
 bundles=thermostat-shared-config-${project.version}.jar, \
         thermostat-keyring-${project.version}.jar, \
+        commons-codec-${commons-codec.version}.jar, \
+        commons-logging-${commons-logging.version}.jar, \
+        commons-collections-${commons-collections.version}.jar, \
+        commons-beanutils-${commons-beanutils.version}.jar, \
         thermostat-storage-core-${project.version}.jar, \
         thermostat-common-core-${project.version}.jar, \
         thermostat-plugin-validator-${project.version}.jar, \
--- a/storage/core/pom.xml	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/pom.xml	Tue Sep 03 14:19:10 2013 +0200
@@ -106,6 +106,18 @@
       <artifactId>org.apache.felix.framework</artifactId>
     </dependency>
     <dependency>
+      <groupId>commons-beanutils</groupId>
+      <artifactId>commons-beanutils</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-collections</groupId>
+      <artifactId>commons-collections</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-logging</groupId>
+      <artifactId>commons-logging</artifactId>
+    </dependency>
+    <dependency>
       <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-annotations</artifactId>
       <version>${project.version}</version>
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementDescriptor.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/core/StatementDescriptor.java	Tue Sep 03 14:19:10 2013 +0200
@@ -21,7 +21,7 @@
      * 
      * @return The statement descriptor.
      */
-    public String getQueryDescriptor() {
+    public String getDescriptor() {
         return desc;
     }
     
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOImpl.java	Tue Sep 03 14:19:10 2013 +0200
@@ -43,7 +43,6 @@
 import java.util.logging.Logger;
 
 import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.CategoryAdapter;
 import com.redhat.thermostat.storage.core.Cursor;
@@ -79,6 +78,19 @@
             + CATEGORY.getName() + " WHERE '" 
             + ALIVE_KEY.getName() + "' = ?b";
     
+    // ADD agent-config SET
+    //                     'agentId' = ?s , \
+    //                     'startTime' = ?l , \
+    //                     'stopTime' = ?l , \
+    //                     'alive' = ?b , \
+    //                     'configListenAddress' = ?s
+    static final String DESC_ADD_AGENT_INFO = "ADD " + CATEGORY.getName() + " SET " +
+            "'" + Key.AGENT_ID.getName() + "' = ?s , " +
+            "'" + AgentInfoDAOImpl.START_TIME_KEY.getName() + "' = ?l , " +
+            "'" + AgentInfoDAOImpl.STOP_TIME_KEY.getName() + "' = ?l , " +
+            "'" + AgentInfoDAOImpl.ALIVE_KEY.getName() + "' = ?b , " +
+            "'" + AgentInfoDAOImpl.CONFIG_LISTEN_ADDRESS.getName() + "' = ?s";
+    
     private final Storage storage;
     private final Category<AggregateCount> aggregateCategory;
     private final ExpressionFactory factory;
@@ -182,9 +194,21 @@
 
     @Override
     public void addAgentInformation(AgentInformation agentInfo) {
-        Add<AgentInformation> replace = storage.createAdd(CATEGORY);
-        replace.setPojo(agentInfo);
-        replace.apply();
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(CATEGORY, DESC_ADD_AGENT_INFO);
+        PreparedStatement<AgentInformation> prepared;
+        try {
+            prepared = storage.prepareStatement(desc);
+            prepared.setString(0, agentInfo.getAgentId());
+            prepared.setLong(1, agentInfo.getStartTime());
+            prepared.setLong(2, agentInfo.getStopTime());
+            prepared.setBoolean(3, agentInfo.isAlive());
+            prepared.setString(4, agentInfo.getConfigListenAddress());
+            prepared.execute();
+        } catch (DescriptorParsingException e) {
+            logger.log(Level.SEVERE, "Preparing stmt '" + desc + "' failed!", e);
+        } catch (StatementExecutionException e) {
+            logger.log(Level.SEVERE, "Executing stmt '" + desc + "' failed!", e);
+        }
     }
 
     @Override
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImpl.java	Tue Sep 03 14:19:10 2013 +0200
@@ -36,11 +36,17 @@
 
 package com.redhat.thermostat.storage.internal.statement;
 
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.ParsedStatement;
 import com.redhat.thermostat.storage.core.PreparedParameter;
 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.Statement;
+import com.redhat.thermostat.storage.core.Update;
+import com.redhat.thermostat.storage.internal.statement.PatchedSetListPojoConverter.IllegalPojoException;
 import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.storage.query.Expression;
 
@@ -53,12 +59,15 @@
  */
 class ParsedStatementImpl<T extends Pojo> implements ParsedStatement<T> {
 
+    private final Statement<T> statement;
+    private final Class<T> dataClass; 
     private int numParams;
     private SuffixExpression suffixExpn;
-    private final Statement<T> statement;
+    private SetList setList;
 
-    ParsedStatementImpl(Statement<T> statement) {
+    ParsedStatementImpl(Statement<T> statement, Class<T> dataClass) {
         this.statement = statement;
+        this.dataClass = dataClass;
     }
     
     @Override
@@ -78,6 +87,7 @@
             IllegalStateException expn = new IllegalStateException(msg);
             throw new IllegalPatchException(expn);
         }
+        patchSetList(params);
         patchWhere(params);
         patchSort(params);
         patchLimit(params);
@@ -86,6 +96,45 @@
         return statement;
     }
 
+    private void patchSetList(PreparedParameter[] params) throws IllegalPatchException {
+        if (setList.getValues().size() == 0) {
+            // no set list, nothing to do
+            return;
+        }
+        // do the patching
+        PatchedSetList patchedSetList = setList.patch(params);
+        // set the values
+        if (statement instanceof Add) {
+            T pojo = convertToPojo(patchedSetList);
+            Add<T> add = (Add<T>)statement;
+            add.setPojo(pojo);
+        }
+        if (statement instanceof Replace) {
+            T pojo = convertToPojo(patchedSetList);
+            Replace<T> replace = (Replace<T>)statement;
+            replace.setPojo(pojo);
+        }
+        if (statement instanceof Update) {
+            Update<T> update = (Update<T>)statement;
+            for (PatchedSetListMember mem: patchedSetList.getSetListMembers()) {
+                @SuppressWarnings("unchecked")
+                Key<Object> key = (Key<Object>)mem.getKey();
+                update.set(key, mem.getValue());
+            }
+        }
+    }
+    
+    private T convertToPojo(PatchedSetList setList) throws IllegalPatchException {
+        PatchedSetListPojoConverter<T> converter = new PatchedSetListPojoConverter<>(setList, dataClass);
+        T pojo = null;
+        try {
+            pojo = converter.convertToPojo();
+        } catch (IllegalPojoException e) {
+            throw new IllegalPatchException(e);
+        }
+        return pojo; 
+    }
+
     private void patchLimit(PreparedParameter[] params) throws IllegalPatchException {
         LimitExpression expn = suffixExpn.getLimitExpn();
         if (expn == null) {
@@ -97,7 +146,7 @@
             Query<T> query = (Query<T>) statement;
             query.limit(patchedExp.getLimitValue());
         } else {
-            String msg = "Patching of non-query types not (yet) supported! Class was:"
+            String msg = "Patching 'limit' of non-query types not supported! Class was:"
                     + statement.getClass().getName();
             IllegalStateException invalid = new IllegalStateException(msg);
             throw new IllegalPatchException(invalid);
@@ -118,7 +167,7 @@
                 query.sort(members[i].getSortKey(), members[i].getDirection());
             }
         } else {
-            String msg = "Patching of non-query types not (yet) supported! Class was:"
+            String msg = "Patching 'sort' of non-query types not supported! Class was:"
                     + statement.getClass().getName();
             IllegalStateException invalid = new IllegalStateException(msg);
             throw new IllegalPatchException(invalid);
@@ -138,8 +187,17 @@
         if (statement instanceof Query) {
             Query<T> query = (Query<T>) statement;
             query.where(whereClause);
+        } else if (statement instanceof Replace) {
+            Replace<T> replace = (Replace<T>) statement;
+            replace.where(whereClause);
+        } else if (statement instanceof Update) {
+            Update<T> update = (Update<T>) statement;
+            update.where(whereClause);
+        } else if (statement instanceof Remove) {
+            Remove<T> remove = (Remove<T>) statement;
+            remove.where(whereClause);
         } else {
-            String msg = "Patching of non-query types not (yet) supported! Class was:"
+            String msg = "Patching of where clause not supported! Class was:"
                     + statement.getClass().getName();
             IllegalStateException invalid = new IllegalStateException(msg);
             throw new IllegalPatchException(invalid);
@@ -158,4 +216,12 @@
         return suffixExpn;
     }
 
+    SetList getSetList() {
+        return setList;
+    }
+
+    void setSetList(SetList setList) {
+        this.setList = setList;
+    }
+
 }
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedExpression.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedExpression.java	Tue Sep 03 14:19:10 2013 +0200
@@ -96,4 +96,23 @@
      */
     int getLimitValue();
     
+}
+
+interface PatchedSetListMemberExpression extends PatchedExpression {
+    
+    /**
+     * 
+     * @return The patched set list member.
+     */
+    PatchedSetListMember getSetListMember();
+}
+
+interface PatchedSetList extends PatchedExpression {
+    
+    /**
+     * @return The patched set list
+     * 
+     */
+    PatchedSetListMember[] getSetListMembers();
+    
 }
\ 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/PatchedSetListMember.java	Tue Sep 03 14:19:10 2013 +0200
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+/**
+ * Data structure representing patched SET lists members.
+ *
+ */
+class PatchedSetListMember {
+
+    private final Key<?> key;
+    private final Object value;
+    
+    PatchedSetListMember(Key<?> key, Object value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    Key<?> getKey() {
+        return key;
+    }
+
+    Object getValue() {
+        return value;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverter.java	Tue Sep 03 14:19:10 2013 +0200
@@ -0,0 +1,95 @@
+/*
+ * 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.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.commons.beanutils.PropertyUtils;
+
+import com.redhat.thermostat.storage.model.Pojo;
+
+class PatchedSetListPojoConverter<T extends Pojo> {
+
+    private final PatchedSetList setList;
+    private final Class<T> dataClass;
+    
+    PatchedSetListPojoConverter(PatchedSetList setList, Class<T> dataClass) {
+        this.setList = setList;
+        this.dataClass = dataClass;
+    }
+    
+    T convertToPojo() throws IllegalPojoException {
+        T instance = null;
+        try {
+            instance = dataClass.newInstance();
+            PatchedSetListMember[] members = setList.getSetListMembers();
+            for (PatchedSetListMember mem: members) {
+                String propName = mem.getKey().getName();
+                PropertyDescriptor desc = PropertyUtils.getPropertyDescriptor(instance, propName);
+                if (desc == null) {
+                    throw new IllegalPojoException("Property " + propName + " not found in Pojo: " + dataClass);
+                }
+                setValueForProperty(instance, desc, mem);
+            }
+        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException
+                | IllegalArgumentException | InvocationTargetException e) {
+            throw new IllegalPojoException(e);
+        }
+        return instance;
+    }
+    
+    private void setValueForProperty(T instance, PropertyDescriptor desc,
+            PatchedSetListMember mem) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        Method writeMethod = desc.getWriteMethod();
+        Object value = mem.getValue();
+        writeMethod.invoke(instance, value);
+    }
+
+    @SuppressWarnings("serial")
+    static class IllegalPojoException extends Exception {
+        
+        IllegalPojoException(Throwable cause) {
+            super(cause);
+        }
+        
+        private IllegalPojoException(String msg) {
+            super(msg);
+        }
+    }
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImpl.java	Tue Sep 03 14:19:10 2013 +0200
@@ -153,6 +153,6 @@
 
     @Override
     public String toString() {
-        return desc.getQueryDescriptor();
+        return desc.getDescriptor();
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SetList.java	Tue Sep 03 14:19:10 2013 +0200
@@ -0,0 +1,96 @@
+/*
+ * 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 com.redhat.thermostat.storage.core.IllegalPatchException;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+
+/**
+ * Represents a set list of a prepared write statement.
+ *
+ */
+class SetList implements Patchable, Printable {
+
+    private List<SetListValue> values = new ArrayList<>();
+    
+    void addValue(SetListValue newValue) {
+        values.add(newValue);
+    }
+    
+    List<SetListValue> getValues() {
+        return values;
+    }
+
+    @Override
+    public void print(int level) {
+        System.out.println("SET:");
+        for (SetListValue val: getValues()) {
+            val.print(level);
+        }
+    }
+
+    @Override
+    public PatchedSetList patch(PreparedParameter[] params)
+            throws IllegalPatchException {
+        List<PatchedSetListMember> patchedSetList = new ArrayList<>();
+        for (SetListValue val: getValues()) {
+            PatchedSetListMemberExpression memberExp = val.patch(params);
+            patchedSetList.add(memberExp.getSetListMember());
+        }
+        PatchedSetListMember[] members = patchedSetList.toArray(new PatchedSetListMember[0]);
+        PatchedSetListImpl setList = new PatchedSetListImpl(members);
+        return setList;
+    }
+    
+    private static class PatchedSetListImpl implements PatchedSetList {
+
+        private final PatchedSetListMember[] members;
+        
+        private PatchedSetListImpl(PatchedSetListMember[] members) {
+            this.members = members;
+        }
+        
+        @Override
+        public PatchedSetListMember[] getSetListMembers() {
+            return members;
+        }
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/SetListValue.java	Tue Sep 03 14:19:10 2013 +0200
@@ -0,0 +1,131 @@
+/*
+ * 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.IllegalPatchException;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.query.LiteralExpression;
+
+/**
+ * Represents a value pair in a set list of prepared writes.
+ *
+ */
+class SetListValue implements Patchable, Printable {
+
+    private TerminalNode key;
+    private TerminalNode value;
+    
+    TerminalNode getKey() {
+        return key;
+    }
+    void setKey(TerminalNode key) {
+        this.key = key;
+    }
+    TerminalNode getValue() {
+        return value;
+    }
+    void setValue(TerminalNode value) {
+        this.value = value;
+    }
+    
+    @Override
+    public boolean equals(Object other) {
+        if (!(other instanceof SetListValue)) {
+            return false;
+        }
+        SetListValue o = (SetListValue)other;
+        return Objects.equals(this.getValue(), o.getValue()) &&
+                Objects.equals(this.getKey(), o.getKey());
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(this.getKey(), this.getValue());
+    }
+    
+    @Override
+    public String toString() {
+        return "{" + getKey() + " = " + getValue() + "}";
+    }
+    
+    @Override
+    public void print(int level) {
+        for (int i = 0; i < level; i++) {
+            System.out.print(" ");
+        }
+        System.out.println(toString());
+        
+    }
+    
+    @Override
+    public PatchedSetListMemberExpression patch(PreparedParameter[] params)
+            throws IllegalPatchException {
+        
+        // patch LHS
+        PatchedWhereExpression keyExp = key.patch(params);
+        @SuppressWarnings("unchecked") // we've generated the key
+        LiteralExpression<Key<?>> keyLiteral = (LiteralExpression<Key<?>>)keyExp.getExpression();
+        Key<?> keyVal = (Key<?>) keyLiteral.getValue();
+        
+        // patch RHS
+        PatchedWhereExpression valExp = value.patch(params);
+        LiteralExpression<?> valLiteral = (LiteralExpression<?>)valExp.getExpression();
+        Object val = valLiteral.getValue();
+        
+        PatchedSetListMember member = new PatchedSetListMember(keyVal, val);
+        PatchedSetListMemberExpressionImpl retval = new PatchedSetListMemberExpressionImpl(member);
+        return retval;
+    }
+    
+    private static class PatchedSetListMemberExpressionImpl implements PatchedSetListMemberExpression {
+
+        private final PatchedSetListMember member;
+        
+        private PatchedSetListMemberExpressionImpl(PatchedSetListMember member) {
+            this.member = member;
+        }
+        
+        @Override
+        public PatchedSetListMember getSetListMember() {
+            return member;
+        }
+        
+    }
+}
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParser.java	Tue Sep 03 14:19:10 2013 +0200
@@ -40,6 +40,7 @@
 import java.util.List;
 import java.util.StringTokenizer;
 
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction;
 import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Category;
@@ -48,7 +49,11 @@
 import com.redhat.thermostat.storage.core.ParsedStatement;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.SortDirection;
+import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.Replace;
+import com.redhat.thermostat.storage.core.Statement;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
 import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
@@ -57,13 +62,28 @@
  * 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):
+ * This parser implements the following simple grammar for statement descriptors.
+ * It supports the following statement types:
+ * <ul>
+ * <li>QUERY (read)</li>
+ * <li>QUERY-COUNT (read)</li>
+ * <li>ADD (write)</li>
+ * <li>UPDATE (write)</li>
+ * <li>REPLACE (write)</li>
+ * <li>REMOVE (write)</li>
+ * </ul>
  * 
+ * <p><strong>Grammar:</strong></p>
  * <pre>
- * statementDesc := statementType category suffix
- * statementType := 'QUERY' | 'QUERY-COUNT'
- * category      := literal
+ * statementDesc := statementType category setList suffix
+ * statementType := 'QUERY' | 'QUERY-COUNT' |
+ *                  'ADD' | 'REPLACE' | 'UPDATE' |
+ *                  'REMOVE'
+ * category      := string
+ * setList       := 'SET' setValues | \empty
+ * setValues     := valuePair valueList
+ * valuePair     := term '=' term
+ * valueList     := ',' setValues | \empty
  * suffix        := 'WHERE' where |
  *                  'SORT' sortCond |
  *                  'LIMIT' term | \empty
@@ -72,7 +92,7 @@
  * orCond        := 'OR' whereExp | \empty
  * sort          := 'SORT' sortCond | \empty
  * sortCond      := sortPair sortList
- * sortPair      := literal sortModifier
+ * sortPair      := term sortModifier
  * sortModifier  := 'ASC' | 'DSC'
  * sortList      := ',' sortCond | \empty
  * limit         := 'LIMIT' term | \empty
@@ -86,9 +106,11 @@
  * sQuote        := \'
  * boolean       := &lt;true&gt; | &lt;false&gt;
  * int           := &lt;literal-int&gt;
- * long          := &lt;literal-long&gt;
+ * long          := &lt;literal-long&gt;longPostFix
+ * longPostFix   := 'l' | 'L'
  * string        := &lt;literal-string-value&gt;
- * compExpRHS    := '!=' term | '=' term | '&lt;=' term | '&gt;=' term | '&lt;' term | '&gt;' term
+ * compExpRHS    := '!=' term | '=' term | '&lt;=' term | '&gt;=' term | 
+ *                  '&lt;' term | '&gt;' term
  * </pre>
  *
  * This implements the following logic precedence rules (in this order of
@@ -105,10 +127,18 @@
 class StatementDescriptorParser<T extends Pojo> {
 
     private static final String TOKEN_DELIMS = " \t\r\n\f";
+    private static final short IDX_QUERY = 0;
+    private static final short IDX_QUERY_COUNT = 1;
+    private static final short IDX_ADD = 2;
+    private static final short IDX_REPLACE = 3;
+    private static final short IDX_UPDATE = 4;
+    private static final short IDX_REMOVE = 5;
     private static final String[] KNOWN_STATEMENT_TYPES = new String[] {
-        "QUERY", "QUERY-COUNT"
+        "QUERY", "QUERY-COUNT", "ADD", "REPLACE", "UPDATE", "REMOVE"
     };
     private static final String SORTLIST_SEP = ",";
+    private static final String SETLIST_SEP = SORTLIST_SEP;
+    private static final String KEYWORD_SET = "SET";
     private static final String KEYWORD_WHERE = "WHERE";
     private static final String KEYWORD_SORT = "SORT";
     private static final String KEYWORD_LIMIT = "LIMIT";
@@ -124,9 +154,10 @@
     // the parsed statement
     private ParsedStatementImpl<T> parsedStatement;
     private SuffixExpression tree;
+    private SetList setList;
     
     StatementDescriptorParser(BackingStorage storage, StatementDescriptor<T> desc) {
-        this.tokens = getTokens(desc.getQueryDescriptor());
+        this.tokens = getTokens(desc.getDescriptor());
         this.currTokenIndex = 0;
         this.placeHolderCount = 0;
         this.desc = desc;
@@ -147,16 +178,126 @@
         matchCategory();
         // matched so far, create the raw statement
         createStatement();
+        this.setList = new SetList();
+        matchSetList(setList);
         this.tree = new SuffixExpression();
         matchSuffix();
         if (currTokenIndex != tokens.length) {
             throw new DescriptorParsingException("Incomplete parse");
         }
         parsedStatement.setNumFreeParams(placeHolderCount);
+        parsedStatement.setSetList(setList);
         parsedStatement.setSuffixExpression(tree);
+        doSemanticAnalysis();
         return parsedStatement;
     }
 
+    private void doSemanticAnalysis() throws DescriptorParsingException {
+        // TODO:
+        // - Check that ADD/REPLACE specifies all keys judging by the Pojo
+        //   model class. Not sure if good idea though, as this would likely
+        //   introduce dep on beanutils.
+        Statement<T> stmt = parsedStatement.getRawStatement();
+        if (stmt == null) {
+            // should never be null
+            throw new NullPointerException();
+        }
+        if (stmt instanceof Add && tree.getWhereExpn() != null) {
+            String msg = "WHERE clause not allowed for ADD";
+            throw new DescriptorParsingException(msg);
+        }
+        if (stmt instanceof Replace && tree.getWhereExpn() == null) {
+            String msg = "WHERE clause required for REPLACE";
+            throw new DescriptorParsingException(msg);
+        }
+        if (stmt instanceof Update) {
+            if (tree.getWhereExpn() == null) {
+                // WHERE required for UPDATE
+                String msg = "WHERE clause required for UPDATE";
+                throw new DescriptorParsingException(msg);
+            }
+            if (setList.getValues().size() == 0) {
+                // SET required for UPDATE
+                String msg = "SET list required for UPDATE";
+                throw new DescriptorParsingException(msg);
+            }
+        }
+        if (stmt instanceof Remove && setList.getValues().size() > 0) {
+            String msg = "SET not allowed for REMOVE";
+            throw new DescriptorParsingException(msg);
+        }
+        if (stmt instanceof Query) {
+            if (setList.getValues().size() > 0) {
+                // Must not have SET for QUERYs
+                String msg = "SET not allowed for QUERY/QUERY-COUNT";
+                throw new DescriptorParsingException(msg);
+            }
+        } else {
+            // only queries can have sort/limit expressions
+            if (this.tree.getLimitExpn() != null || this.tree.getSortExpn() != null) {
+                String msg = "LIMIT/SORT only allowed for QUERY/QUERY-COUNT";
+                throw new DescriptorParsingException(msg);
+            }
+        }
+    }
+
+    /*
+     * Match set list for DML statements.
+     */
+    private void matchSetList(final SetList setList) throws DescriptorParsingException {
+        if (tokens.length == currTokenIndex) {
+            // no set list
+            return;
+        }
+        if (tokens[currTokenIndex].equals(KEYWORD_SET)) {
+            currTokenIndex++; // SET
+            matchSetValues(setList);
+        }
+        // empty, proceed with suffix
+    }
+
+    /*
+     * Match list of values in a SET expression 
+     */
+    private void matchSetValues(SetList setList) throws DescriptorParsingException {
+        matchValuePair(setList);
+        matchValueList(setList);
+    }
+
+    /*
+     * Match more value pairs in a SET list
+     */
+    private void matchValueList(SetList setList) throws DescriptorParsingException {
+        if (currTokenIndex == tokens.length) {
+            // empty
+            return;
+        }
+        if (tokens[currTokenIndex].equals(SETLIST_SEP)) {
+            currTokenIndex++; // ,
+            matchSetValues(setList);
+        }
+    }
+
+    /*
+     * Match one pair of values in a 
+     */
+    private void matchValuePair(SetList setList) throws DescriptorParsingException {
+        SetListValue value = new SetListValue();
+        TerminalNode lval = new TerminalNode(null);
+        matchTerm(lval, true);
+        value.setKey(lval);
+        if (tokens[currTokenIndex].equals("=")) {
+            currTokenIndex++; // =
+        } else {
+            String msg = "Expected '=' after SET value LHS. Token was ->" + tokens[currTokenIndex] + "<-";
+            throw new DescriptorParsingException(msg);
+        }
+        TerminalNode rval = new TerminalNode(null);
+        matchTerm(rval, false);
+        value.setValue(rval);
+        setList.addValue(value);
+    }
+
     /*
      * Match optional suffixes. 
      */
@@ -579,23 +720,42 @@
     }
 
     private void createStatement() {
-        if (tokens[0].equals(KNOWN_STATEMENT_TYPES[0])) {
+        // matchStatementType and matchCategory advanced currTokenIndex,
+        // lets use idx of 0 here.
+        final String statementType = tokens[0];
+        Class<T> dataClass = desc.getCategory().getDataClass();
+        if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) {
             // regular query case
             Query<T> query = storage.createQuery(desc.getCategory());
-            this.parsedStatement = new ParsedStatementImpl<>(query);
-        } else if (tokens[0].equals(KNOWN_STATEMENT_TYPES[1])) {
+            this.parsedStatement = new ParsedStatementImpl<>(query, dataClass);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_QUERY_COUNT])) {
             // create aggregate count query
             Query<T> query = storage.createAggregateQuery(AggregateFunction.COUNT, desc.getCategory());
-            this.parsedStatement = new ParsedStatementImpl<>(query);
-        }
-        else {
-            throw new IllegalStateException("Don't know how to create statement type '" + tokens[0] + "'");
+            this.parsedStatement = new ParsedStatementImpl<>(query, dataClass);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) {
+            // create add
+            Add<T> add = storage.createAdd(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(add, dataClass);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) {
+            // create replace
+            Replace<T> replace = storage.createReplace(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(replace, dataClass);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) {
+            // create replace
+            Update<T> update = storage.createUpdate(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(update, dataClass);
+        } else if (statementType.equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) {
+            // create remove
+            Remove<T> remove = storage.createRemove(desc.getCategory());
+            this.parsedStatement = new ParsedStatementImpl<>(remove, dataClass);
+        } else {
+            throw new IllegalStateException("Don't know how to create statement type '" + statementType + "'");
         }
     }
 
     private void matchCategory() throws DescriptorParsingException {
         if (currTokenIndex >= tokens.length) {
-            throw new DescriptorParsingException("Missing category name in descriptor: '" + desc.getQueryDescriptor() + "'");
+            throw new DescriptorParsingException("Missing category name in descriptor: '" + desc.getDescriptor() + "'");
         }
         Category<?> category = desc.getCategory();
         if (!tokens[currTokenIndex].equals(category.getName())) {
@@ -609,10 +769,23 @@
     }
 
     private void matchStatementType() throws DescriptorParsingException {
-        // matches 'QUERY' and 'QUERY-COUNT' only at this point
-        if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[0])) {
+        if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_QUERY])) {
+            // QUERY
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_QUERY_COUNT])) {
+            // QUERY-COUNT
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_ADD])) {
+            // ADD
             currTokenIndex++;
-        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[1])) {
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_REPLACE])) {
+            // REPLACE
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_UPDATE])) {
+            // UPDATE
+            currTokenIndex++;
+        } else if (tokens[currTokenIndex].equals(KNOWN_STATEMENT_TYPES[IDX_REMOVE])) {
+            // REMOVE
             currTokenIndex++;
         } else {
             throw new DescriptorParsingException("Unknown statement type: '" + tokens[currTokenIndex] + "'");
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/statement/UnfinishedValueNode.java	Tue Sep 03 14:19:10 2013 +0200
@@ -83,7 +83,8 @@
     
     @Override
     public String toString() {
-        return "Unfinished value (" + getParameterIndex() + ")";
+        return "Unfinished value (" + getParameterIndex() + ") " + getType() +
+                                 ":" + ( isLHS ? "LHS" : "RHS" );
     }
     
     @Override
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOTest.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/AgentInfoDAOTest.java	Tue Sep 03 14:19:10 2013 +0200
@@ -51,8 +51,9 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
 
-import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
@@ -102,6 +103,12 @@
         assertEquals(expectedAliveAgents, AgentInfoDAOImpl.QUERY_ALIVE_AGENTS);
         String aggregateAllAgents = "QUERY-COUNT agent-config";
         assertEquals(aggregateAllAgents, AgentInfoDAOImpl.AGGREGATE_COUNT_ALL_AGENTS);
+        String addAgentInfo = "ADD agent-config SET 'agentId' = ?s , " +
+                                                   "'startTime' = ?l , " +
+                                                   "'stopTime' = ?l , " +
+                                                   "'alive' = ?b , " +
+                                                   "'configListenAddress' = ?s";
+        assertEquals(addAgentInfo, AgentInfoDAOImpl.DESC_ADD_AGENT_INFO);
     }
     
     @Test
@@ -256,20 +263,30 @@
         assertSame(expected, computed);
     }
 
+    @SuppressWarnings("unchecked")
     @Test
-    public void verifyAddAgentInformation() {
+    public void verifyAddAgentInformation() throws StatementExecutionException, DescriptorParsingException {
         Storage storage = mock(Storage.class);
-        @SuppressWarnings("unchecked")
-        Add<AgentInformation> add = mock(Add.class);
-        when(storage.createAdd(eq(AgentInfoDAO.CATEGORY))).thenReturn(add);
+        PreparedStatement<AgentInformation> add = mock(PreparedStatement.class);
+        when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(add);
 
         AgentInfoDAO dao = new AgentInfoDAOImpl(storage);
 
         dao.addAgentInformation(agentInfo1);
 
-        verify(storage).createAdd(AgentInfoDAO.CATEGORY);
-        verify(add).setPojo(agentInfo1);
-        verify(add).apply();
+        @SuppressWarnings("rawtypes")
+        ArgumentCaptor<StatementDescriptor> captor = ArgumentCaptor.forClass(StatementDescriptor.class);
+        
+        verify(storage).prepareStatement(captor.capture());
+        StatementDescriptor<?> desc = captor.getValue();
+        assertEquals(AgentInfoDAOImpl.DESC_ADD_AGENT_INFO, desc.getDescriptor());
+        verify(add).setString(0, agentInfo1.getAgentId());
+        verify(add).setLong(1, agentInfo1.getStartTime());
+        verify(add).setLong(2, agentInfo1.getStopTime());
+        verify(add).setBoolean(3, agentInfo1.isAlive());
+        verify(add).setString(4, agentInfo1.getConfigListenAddress());
+        verify(add).execute();
+        Mockito.verifyNoMoreInteractions(add);
     }
 
     @Test
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/ParsedStatementImplTest.java	Tue Sep 03 14:19:10 2013 +0200
@@ -48,18 +48,23 @@
 import org.junit.Test;
 
 import com.redhat.thermostat.common.Pair;
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DataModifyingStatement;
 import com.redhat.thermostat.storage.core.IllegalPatchException;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.PreparedParameter;
 import com.redhat.thermostat.storage.core.Query;
 import com.redhat.thermostat.storage.core.Query.SortDirection;
+import com.redhat.thermostat.storage.core.Replace;
+import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.Pojo;
 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.ExpressionFactory;
 import com.redhat.thermostat.storage.query.LiteralExpression;
 
 public class ParsedStatementImplTest {
@@ -79,10 +84,11 @@
     @Test
     public void canPatchWhereAndExpr() throws IllegalPatchException {
         // create the parsedStatementImpl we are going to use
-        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null);
         SuffixExpression suffixExpn = new SuffixExpression();
         suffixExpn.setLimitExpn(null);
         suffixExpn.setSortExpn(null);
+        parsedStmt.setSetList(new SetList());
         // WHERE a = ? AND c = ?
         WhereExpression expn = new WhereExpression();
         BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
@@ -152,10 +158,11 @@
     @Test
     public void canPatchBasicWhereEquals() throws IllegalPatchException {
         // create the parsedStatementImpl we are going to use
-        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null);
         SuffixExpression suffixExpn = new SuffixExpression();
         suffixExpn.setLimitExpn(null);
         suffixExpn.setSortExpn(null);
+        parsedStmt.setSetList(new SetList());
         // WHERE a = ?
         WhereExpression expn = new WhereExpression();
         BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
@@ -215,10 +222,11 @@
     @Test
     public void canPatchBasicWhereEqualsLHSKeyAndRHSValue() throws IllegalPatchException {
         // create the parsedStatementImpl we are going to use
-        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null);
         SuffixExpression suffixExpn = new SuffixExpression();
         suffixExpn.setLimitExpn(null);
         suffixExpn.setSortExpn(null);
+        parsedStmt.setSetList(new SetList());
         // WHERE ?s = ?b
         WhereExpression expn = new WhereExpression();
         BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
@@ -284,7 +292,8 @@
     @Test
     public void canPatchBasicLimit() throws IllegalPatchException {
         // create the parsedStatementImpl we are going to use
-        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null);
+        parsedStmt.setSetList(new SetList());
         SuffixExpression suffixExpn = new SuffixExpression();
         LimitExpression limitExpnToPatch = new LimitExpression();
         UnfinishedLimitValue unfinished = new UnfinishedLimitValue();
@@ -305,10 +314,221 @@
         assertEquals(3, q.limitVal);
     }
     
+    private SetList buildSetList() {
+        // Build this set list, which corresponds to the TestPojo below
+        // SET 'writerId' = ?s , 'fooTimeStamp' = ?l
+        SetList setList = new SetList();
+        SetListValue writerId = new SetListValue();
+        TerminalNode writerKey = new TerminalNode(null);
+        writerKey.setValue(new Key<>("writerId"));
+        TerminalNode writerValue = new TerminalNode(null);
+        UnfinishedValueNode unfinishedWriter = new UnfinishedValueNode();
+        unfinishedWriter.setParameterIndex(0);
+        unfinishedWriter.setType(String.class);
+        unfinishedWriter.setLHS(false);
+        writerValue.setValue(unfinishedWriter);
+        writerId.setKey(writerKey);
+        writerId.setValue(writerValue);
+        setList.addValue(writerId);
+        SetListValue fooTimeStamp = new SetListValue();
+        TerminalNode timeStampKey = new TerminalNode(null);
+        timeStampKey.setValue(new Key<>("fooTimeStamp"));
+        fooTimeStamp.setKey(timeStampKey);
+        TerminalNode timeStampVal = new TerminalNode(null);
+        UnfinishedValueNode timeStampUnfinished = new UnfinishedValueNode();
+        timeStampUnfinished.setLHS(false);
+        timeStampUnfinished.setParameterIndex(1);
+        timeStampUnfinished.setType(Long.class);
+        timeStampVal.setValue(timeStampUnfinished);
+        fooTimeStamp.setValue(timeStampVal);
+        setList.addValue(fooTimeStamp);
+        return setList;
+    }
+    
+    /*
+     * Test for patching of:
+     *  "ADD something SET 'writerId' = ?s, 'fooTimeStamp' = ?l"
+     */
+    @Test
+    public void canPatchBasicSetListAdd() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        DataModifyingStatement<TestPojo> stmt = new TestAdd();
+        ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        SetList setList = buildSetList();
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        suffixExpn.setWhereExpn(null);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        assertEquals(2, setList.getValues().size());
+        parsedStmt.setSetList(setList);
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
+        preparedStatement.setString(0, "foo-writer");
+        preparedStatement.setLong(1, Long.MAX_VALUE);
+        PreparedParameter[] params = preparedStatement.getParams();
+        // finally test the patching
+        Add<TestPojo> add = (Add<TestPojo>)parsedStmt.patchStatement(params);
+        assertTrue(add instanceof TestAdd);
+        TestAdd q = (TestAdd)add;
+        Pojo testPojo = q.pojo;
+        assertTrue(testPojo instanceof TestPojo);
+        TestPojo tPojo = (TestPojo)testPojo;
+        assertEquals("foo-writer", tPojo.getWriterId());
+        assertEquals(Long.MAX_VALUE, tPojo.getFooTimeStamp());
+    }
+    
+    /*
+     * Test for patching of:
+     *  "REPLACE something SET ?s = 'foo-bar', 'fooTimeStamp' = ?l WHERE 'foo' = ?i"
+     */
+    @Test
+    public void canPatchBasicSetListReplace() throws IllegalPatchException {
+        DataModifyingStatement<TestPojo> stmt = new TestReplace();
+        ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        
+        // Build this set list, which corresponds to the TestPojo below
+        // SET ?s = 'foo-bar' , 'fooTimeStamp' = ?l
+        SetList setList = new SetList();
+        SetListValue writerId = new SetListValue();
+        TerminalNode writerKey = new TerminalNode(null);
+        UnfinishedValueNode unfinishedWriterKey = new UnfinishedValueNode();
+        unfinishedWriterKey.setParameterIndex(0);
+        unfinishedWriterKey.setType(String.class);
+        unfinishedWriterKey.setLHS(true);
+        writerKey.setValue(unfinishedWriterKey);
+        writerId.setKey(writerKey);
+        TerminalNode writerValue = new TerminalNode(null);
+        writerValue.setValue("foo-bar");
+        writerId.setValue(writerValue);
+        setList.addValue(writerId);
+        SetListValue fooTimeStamp = new SetListValue();
+        TerminalNode timeStampKey = new TerminalNode(null);
+        timeStampKey.setValue(new Key<>("fooTimeStamp"));
+        fooTimeStamp.setKey(timeStampKey);
+        TerminalNode timeStampVal = new TerminalNode(null);
+        UnfinishedValueNode timeStampUnfinished = new UnfinishedValueNode();
+        timeStampUnfinished.setLHS(false);
+        timeStampUnfinished.setParameterIndex(1);
+        timeStampUnfinished.setType(Long.class);
+        timeStampVal.setValue(timeStampUnfinished);
+        fooTimeStamp.setValue(timeStampVal);
+        setList.addValue(fooTimeStamp);
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        
+        // WHERE 'foo' = ?i
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode equals = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(equals);
+        equals.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode fooPatch = new TerminalNode(equals);
+        UnfinishedValueNode patch2 = new UnfinishedValueNode();
+        patch2.setLHS(false);
+        patch2.setType(Integer.class);
+        patch2.setParameterIndex(2);
+        fooPatch.setValue(patch2);
+        TerminalNode foo = new TerminalNode(equals);
+        foo.setValue(new Key<>("foo"));
+        equals.setLeftChild(foo);
+        equals.setRightChild(fooPatch);
+        suffixExpn.setWhereExpn(where);
+        
+        parsedStmt.setSuffixExpression(suffixExpn);
+        assertEquals(2, setList.getValues().size());
+        parsedStmt.setSetList(setList);
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(3);
+        preparedStatement.setString(0, "writerId");
+        preparedStatement.setLong(1, Long.MAX_VALUE);
+        preparedStatement.setInt(2, -400);
+        PreparedParameter[] params = preparedStatement.getParams();
+        // finally test the patching
+        Replace<TestPojo> replace = (Replace<TestPojo>)parsedStmt.patchStatement(params);
+        assertTrue(replace instanceof TestReplace);
+        TestReplace q = (TestReplace)replace;
+        Pojo testPojo = q.pojo;
+        assertTrue(testPojo instanceof TestPojo);
+        TestPojo tPojo = (TestPojo)testPojo;
+        assertEquals("foo-bar", tPojo.getWriterId());
+        assertEquals(Long.MAX_VALUE, tPojo.getFooTimeStamp());
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression expectedExpression = factory.equalTo(new Key<>("foo"), -400);
+        assertEquals(expectedExpression, q.where);
+    }
+    
+    /*
+     * Test for patching of:
+     *  "UPDATE something SET 'writerId' = ?s WHERE 'foo' = ?i"
+     */
+    @Test
+    public void canPatchBasicSetListUpdate() throws IllegalPatchException {
+        DataModifyingStatement<TestPojo> stmt = new TestUpdate();
+        ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        
+        // Build this set list, which corresponds to the TestPojo below
+        // SET 'writerId' = ?s
+        SetList setList = new SetList();
+        SetListValue writerId = new SetListValue();
+        TerminalNode writerKey = new TerminalNode(null);
+        writerKey.setValue(new Key<>("writerId"));
+        TerminalNode writerValue = new TerminalNode(null);
+        UnfinishedValueNode unfinishedWriterValue = new UnfinishedValueNode();
+        unfinishedWriterValue.setParameterIndex(0);
+        unfinishedWriterValue.setType(String.class);
+        unfinishedWriterValue.setLHS(false);
+        writerValue.setValue(unfinishedWriterValue);
+        writerId.setKey(writerKey);
+        writerId.setValue(writerValue);
+        setList.addValue(writerId);
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        
+        // WHERE 'foo' = ?i
+        WhereExpression where = new WhereExpression();
+        BinaryExpressionNode equals = new BinaryExpressionNode(where.getRoot());
+        where.getRoot().setValue(equals);
+        equals.setOperator(BinaryComparisonOperator.EQUALS);
+        TerminalNode fooPatch = new TerminalNode(equals);
+        UnfinishedValueNode patch2 = new UnfinishedValueNode();
+        patch2.setLHS(false);
+        patch2.setType(Integer.class);
+        patch2.setParameterIndex(1);
+        fooPatch.setValue(patch2);
+        TerminalNode foo = new TerminalNode(equals);
+        foo.setValue(new Key<>("foo"));
+        equals.setLeftChild(foo);
+        equals.setRightChild(fooPatch);
+        suffixExpn.setWhereExpn(where);
+        
+        parsedStmt.setSuffixExpression(suffixExpn);
+        assertEquals(1, setList.getValues().size());
+        parsedStmt.setSetList(setList);
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
+        preparedStatement.setString(0, "foobar-writer-id");
+        preparedStatement.setInt(1, -400);
+        PreparedParameter[] params = preparedStatement.getParams();
+        // finally test the patching
+        Update<TestPojo> replace = (Update<TestPojo>)parsedStmt.patchStatement(params);
+        assertTrue(replace instanceof TestUpdate);
+        TestUpdate q = (TestUpdate)replace;
+        List<Pair<Object, Object>> updates = q.updates;
+        assertEquals(1, updates.size());
+        Pair<Object, Object> update = updates.get(0);
+        assertEquals(new Key<>("writerId"), update.getFirst());
+        assertEquals("foobar-writer-id", update.getSecond());
+        
+        ExpressionFactory factory = new ExpressionFactory();
+        Expression expectedExpression = factory.equalTo(new Key<>("foo"), -400);
+        assertEquals(expectedExpression, q.where);
+    }
+    
     @Test
     public void canPatchBasicSort() throws IllegalPatchException {
         // create the parsedStatementImpl we are going to use
-        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null);
+        parsedStmt.setSetList(new SetList());
         SuffixExpression suffixExpn = new SuffixExpression();
         // SORT ? ASC, b DSC
         SortExpression sortExpn = new SortExpression();
@@ -349,9 +569,63 @@
     }
     
     @Test
+    public void failPatchSetListAddWrongType() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        DataModifyingStatement<TestPojo> stmt = new TestAdd();
+        ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        SetList setList = buildSetList();
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        suffixExpn.setWhereExpn(null);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        assertEquals(2, setList.getValues().size());
+        parsedStmt.setSetList(setList);
+        // set the value for the one unfinished param
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(2);
+        preparedStatement.setLong(0, -1);
+        preparedStatement.setString(1, "foobar");
+        PreparedParameter[] params = preparedStatement.getParams();
+        // this should fail since types don't match
+        try {
+            parsedStmt.patchStatement(params);
+            fail("Should have failed to patch, due to type mismatch");
+        } catch (IllegalPatchException e) {
+            assertTrue(e.getMessage().contains("Expected " + String.class.getName()));
+            // pass
+        }
+    }
+    
+    @Test
+    public void failPatchSetListAddInsufficientParams() throws IllegalPatchException {
+        // create the parsedStatementImpl we are going to use
+        DataModifyingStatement<TestPojo> stmt = new TestAdd();
+        ParsedStatementImpl<TestPojo> parsedStmt = new ParsedStatementImpl<>(stmt, TestPojo.class);
+        SuffixExpression suffixExpn = new SuffixExpression();
+        SetList setList = buildSetList();
+        suffixExpn.setLimitExpn(null);
+        suffixExpn.setSortExpn(null);
+        suffixExpn.setWhereExpn(null);
+        parsedStmt.setSuffixExpression(suffixExpn);
+        assertEquals(2, setList.getValues().size());
+        parsedStmt.setSetList(setList);
+        // set the value for the one unfinished param
+        PreparedStatementImpl<Pojo> preparedStatement = new PreparedStatementImpl<>(0);
+        PreparedParameter[] params = preparedStatement.getParams();
+        // this should fail since types don't match
+        try {
+            parsedStmt.patchStatement(params);
+            fail("Should have failed to patch, due to type mismatch");
+        } catch (IllegalPatchException e) {
+            // pass
+        }
+    }
+    
+    @Test
     public void failPatchWithWrongType() throws IllegalPatchException {
         // create the parsedStatementImpl we are going to use
-        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null);
+        parsedStmt.setSetList(new SetList());
         SuffixExpression suffixExpn = new SuffixExpression();
         suffixExpn.setLimitExpn(null);
         suffixExpn.setSortExpn(null);
@@ -408,10 +682,11 @@
     @Test
     public void failPatchBasicEqualsIfIndexOutofBounds() {
         // create the parsedStatementImpl we are going to use
-        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement);
+        ParsedStatementImpl<Pojo> parsedStmt = new ParsedStatementImpl<>(statement, null);
         SuffixExpression suffixExpn = new SuffixExpression();
         suffixExpn.setLimitExpn(null);
         suffixExpn.setSortExpn(null);
+        parsedStmt.setSetList(new SetList());
         // WHERE a = ?
         WhereExpression expn = new WhereExpression();
         BinaryExpressionNode and = new BinaryExpressionNode(expn.getRoot());
@@ -470,14 +745,100 @@
         @Override
         public Cursor<Pojo> execute() {
             // Not implemented
-            return null;
+            throw new AssertionError();
         }
 
         @Override
         public Expression getWhereExpression() {
             // Not implemented
-            return null;
+            throw new AssertionError();
+        }
+        
+    }
+    
+    private static class TestAdd implements Add<TestPojo> {
+        
+        private Pojo pojo; 
+
+        @Override
+        public void setPojo(Pojo pojo) {
+            this.pojo = pojo;
+        }
+
+        @Override
+        public int apply() {
+            // not implemented
+            throw new AssertionError();
+        }
+        
+    }
+    
+    private static class TestReplace implements Replace<TestPojo> {
+
+        private Expression where;
+        private Pojo pojo;
+        
+        @Override
+        public void setPojo(Pojo pojo) {
+            this.pojo = pojo;
+        }
+
+        @Override
+        public void where(Expression expression) {
+            where = expression;
+        }
+
+        @Override
+        public int apply() {
+            // not implemented
+            throw new AssertionError();
         }
         
     }
+    
+    private static class TestUpdate implements Update<TestPojo> {
+
+        private Expression where;
+        private List<Pair<Object, Object>> updates = new ArrayList<>();
+        
+        @Override
+        public void where(Expression expr) {
+            this.where = expr;
+        }
+
+        @SuppressWarnings({ "rawtypes", "unchecked" })
+        @Override
+        public <S> void set(Key<S> key, S value) {
+            Pair update = new Pair<>(key, value);
+            updates.add(update);
+        }
+
+        @Override
+        public int apply() {
+            // not implemented
+            throw new AssertionError();
+        }
+        
+    }
+    
+    public static class TestPojo implements Pojo {
+        
+        private String writerId;
+        private long fooTimeStamp;
+        
+        public String getWriterId() {
+            return writerId;
+        }
+        public void setWriterId(String writerId) {
+            this.writerId = writerId;
+        }
+        public long getFooTimeStamp() {
+            return fooTimeStamp;
+        }
+        public void setFooTimeStamp(long fooTimeStamp) {
+            this.fooTimeStamp = fooTimeStamp;
+        }
+        
+        
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PatchedSetListPojoConverterTest.java	Tue Sep 03 14:19:10 2013 +0200
@@ -0,0 +1,105 @@
+/*
+ * 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 org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.internal.statement.PatchedSetListPojoConverter.IllegalPojoException;
+import com.redhat.thermostat.storage.model.Pojo;
+
+public class PatchedSetListPojoConverterTest {
+
+    @Test
+    public void testBasicConversion() throws IllegalPojoException {
+        PatchedSetListMember mem1 = new PatchedSetListMember(new Key<>("foo"), "foo-val");
+        PatchedSetListMember mem2 = new PatchedSetListMember(new Key<>("barKey"), Long.MAX_VALUE);
+        PatchedSetListMember[] members = new PatchedSetListMember[] {
+                mem1,
+                mem2
+        };
+        PatchedSetList setList = mock(PatchedSetList.class);
+        when(setList.getSetListMembers()).thenReturn(members);
+        PatchedSetListPojoConverter<TestMe> converter = new PatchedSetListPojoConverter<>(setList, TestMe.class);
+        TestMe instance = converter.convertToPojo();
+        assertEquals("foo-val", instance.getFoo());
+        assertEquals(Long.MAX_VALUE, instance.getBarKey());
+    }
+    
+    @Test
+    public void testConversionFailBasic() {
+        PatchedSetListMember mem1 = new PatchedSetListMember(new Key<>("wrong-Prop"), "foo-val");
+        PatchedSetListMember[] members = new PatchedSetListMember[] {
+                mem1
+        };
+        PatchedSetList setList = mock(PatchedSetList.class);
+        when(setList.getSetListMembers()).thenReturn(members);
+        PatchedSetListPojoConverter<TestMe> converter = new PatchedSetListPojoConverter<>(setList, TestMe.class);
+        try {
+            converter.convertToPojo();
+            fail("Should not convert, property not present in Pojo");
+        } catch (IllegalPojoException e) {
+            // pass
+            assertTrue(e.getMessage().contains("Property wrong-Prop not found in Pojo:"));
+        }
+    }
+    
+    public static class TestMe implements Pojo {
+        
+        private String foo;
+        private long barKey;
+
+        public String getFoo() {
+            return foo;
+        }
+        public void setFoo(String foo) {
+            this.foo = foo;
+        }
+        public long getBarKey() {
+            return barKey;
+        }
+        public void setBarKey(long barKey) {
+            this.barKey = barKey;
+        }
+    }
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/PreparedStatementImplTest.java	Tue Sep 03 14:19:10 2013 +0200
@@ -37,20 +37,30 @@
 package com.redhat.thermostat.storage.internal.statement;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.junit.Test;
 
+import com.redhat.thermostat.common.Pair;
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.Cursor;
 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.StatementDescriptor;
 import com.redhat.thermostat.storage.core.StatementExecutionException;
+import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.model.Pojo;
 import com.redhat.thermostat.storage.query.BinaryComparisonExpression;
 import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
@@ -95,7 +105,7 @@
         String queryString = "QUERY foo WHERE 'a' = ?s";
         @SuppressWarnings("unchecked")
         StatementDescriptor<Pojo> desc = (StatementDescriptor<Pojo>) mock(StatementDescriptor.class);
-        when(desc.getQueryDescriptor()).thenReturn(queryString);
+        when(desc.getDescriptor()).thenReturn(queryString);
         @SuppressWarnings("unchecked")
         Category<Pojo> mockCategory = (Category<Pojo>) mock(Category.class);
         when(desc.getCategory()).thenReturn(mockCategory);
@@ -115,11 +125,144 @@
     }
     
     @Test
+    public void canDoParsingPatchingAndExecutionForAdd() throws Exception {
+        String addString = "ADD foo-table SET 'foo' = ?s";
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<FooPojo> desc = (StatementDescriptor<FooPojo>) mock(StatementDescriptor.class);
+        when(desc.getDescriptor()).thenReturn(addString);
+        @SuppressWarnings("unchecked")
+        Category<FooPojo> mockCategory = (Category<FooPojo>) mock(Category.class);
+        when(desc.getCategory()).thenReturn(mockCategory);
+        when(mockCategory.getDataClass()).thenReturn(FooPojo.class);
+        when(mockCategory.getName()).thenReturn("foo-table");
+        BackingStorage storage = mock(BackingStorage.class);
+        TestAdd add = new TestAdd();
+        when(storage.createAdd(mockCategory)).thenReturn(add);
+        PreparedStatement<FooPojo> preparedStatement = new PreparedStatementImpl<FooPojo>(storage, desc);
+        preparedStatement.setString(0, "foo-val");
+        assertFalse(add.executed);
+        try {
+            // this should call add.apply();
+            preparedStatement.execute();
+        } catch (StatementExecutionException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(add.pojo != null);
+        assertTrue(add.pojo instanceof FooPojo);
+        assertTrue(add.executed);
+        FooPojo fooPojo = (FooPojo)add.pojo;
+        assertEquals("foo-val", fooPojo.getFoo());
+    }
+    
+    @Test
+    public void canDoParsingPatchingAndExecutionForUpdate() throws Exception {
+        String addString = "UPDATE foo-table SET 'foo' = ?s WHERE 'foo' = ?s";
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<FooPojo> desc = (StatementDescriptor<FooPojo>) mock(StatementDescriptor.class);
+        when(desc.getDescriptor()).thenReturn(addString);
+        @SuppressWarnings("unchecked")
+        Category<FooPojo> mockCategory = (Category<FooPojo>) mock(Category.class);
+        when(desc.getCategory()).thenReturn(mockCategory);
+        when(mockCategory.getDataClass()).thenReturn(FooPojo.class);
+        when(mockCategory.getName()).thenReturn("foo-table");
+        BackingStorage storage = mock(BackingStorage.class);
+        TestUpdate update = new TestUpdate();
+        when(storage.createUpdate(mockCategory)).thenReturn(update);
+        PreparedStatement<FooPojo> preparedStatement = new PreparedStatementImpl<FooPojo>(storage, desc);
+        preparedStatement.setString(0, "foo-val");
+        preparedStatement.setString(1, "nice");
+        assertFalse(update.executed);
+        try {
+            // this should call apply();
+            preparedStatement.execute();
+        } catch (StatementExecutionException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(update.executed);
+        assertEquals(1, update.updates.size());
+        Pair<Key<Object>, Object> item = update.updates.get(0);
+        assertEquals(new Key<>("foo"), item.getFirst());
+        assertEquals("foo-val", item.getSecond());
+        LiteralExpression<Key<String>> o1 = new LiteralExpression<>(new Key<String>("foo"));
+        LiteralExpression<String> o2 = new LiteralExpression<>("nice"); 
+        BinaryComparisonExpression<String> binComp = new BinaryComparisonExpression<>(
+                o1, BinaryComparisonOperator.EQUALS, o2);
+        assertEquals(binComp, update.where);
+    }
+    
+    @Test
+    public void canDoParsingPatchingAndExecutionForReplace() throws Exception {
+        String addString = "REPLACE foo-table SET 'foo' = ?s WHERE 'foo' = ?s";
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<FooPojo> desc = (StatementDescriptor<FooPojo>) mock(StatementDescriptor.class);
+        when(desc.getDescriptor()).thenReturn(addString);
+        @SuppressWarnings("unchecked")
+        Category<FooPojo> mockCategory = (Category<FooPojo>) mock(Category.class);
+        when(desc.getCategory()).thenReturn(mockCategory);
+        when(mockCategory.getDataClass()).thenReturn(FooPojo.class);
+        when(mockCategory.getName()).thenReturn("foo-table");
+        BackingStorage storage = mock(BackingStorage.class);
+        TestReplace replace = new TestReplace();
+        when(storage.createReplace(mockCategory)).thenReturn(replace);
+        PreparedStatement<FooPojo> preparedStatement = new PreparedStatementImpl<FooPojo>(storage, desc);
+        preparedStatement.setString(0, "foo-val");
+        preparedStatement.setString(1, "bar");
+        assertFalse(replace.executed);
+        try {
+            // this should call apply();
+            preparedStatement.execute();
+        } catch (StatementExecutionException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(replace.pojo != null);
+        assertTrue(replace.pojo instanceof FooPojo);
+        assertTrue(replace.executed);
+        FooPojo fooPojo = (FooPojo)replace.pojo;
+        assertEquals("foo-val", fooPojo.getFoo());
+        LiteralExpression<Key<String>> o1 = new LiteralExpression<>(new Key<String>("foo"));
+        LiteralExpression<String> o2 = new LiteralExpression<>("bar"); 
+        BinaryComparisonExpression<String> binComp = new BinaryComparisonExpression<>(
+                o1, BinaryComparisonOperator.EQUALS, o2);
+        assertEquals(binComp, replace.where);
+    }
+    
+    @Test
+    public void canDoParsingPatchingAndExecutionForRemove() throws Exception {
+        String addString = "REMOVE foo-table WHERE 'fooRem' = ?s";
+        @SuppressWarnings("unchecked")
+        StatementDescriptor<FooPojo> desc = (StatementDescriptor<FooPojo>) mock(StatementDescriptor.class);
+        when(desc.getDescriptor()).thenReturn(addString);
+        @SuppressWarnings("unchecked")
+        Category<FooPojo> mockCategory = (Category<FooPojo>) mock(Category.class);
+        when(desc.getCategory()).thenReturn(mockCategory);
+        when(mockCategory.getDataClass()).thenReturn(FooPojo.class);
+        when(mockCategory.getName()).thenReturn("foo-table");
+        BackingStorage storage = mock(BackingStorage.class);
+        TestRemove remove = new TestRemove();
+        when(storage.createRemove(mockCategory)).thenReturn(remove);
+        PreparedStatement<FooPojo> preparedStatement = new PreparedStatementImpl<FooPojo>(storage, desc);
+        preparedStatement.setString(0, "bar");
+        assertFalse(remove.executed);
+        try {
+            // this should call apply();
+            preparedStatement.execute();
+        } catch (StatementExecutionException e) {
+            fail(e.getMessage());
+        }
+        assertTrue(remove.executed);
+        LiteralExpression<Key<String>> o1 = new LiteralExpression<>(new Key<String>("fooRem"));
+        LiteralExpression<String> o2 = new LiteralExpression<>("bar"); 
+        BinaryComparisonExpression<String> binComp = new BinaryComparisonExpression<>(
+                o1, BinaryComparisonOperator.EQUALS, o2);
+        assertEquals(binComp, remove.where);
+    }
+    
+    @Test
     public void failExecutionWithWronglyTypedParams() throws Exception {
         String queryString = "QUERY foo WHERE 'a' = ?b";
         @SuppressWarnings("unchecked")
         StatementDescriptor<Pojo> desc = (StatementDescriptor<Pojo>) mock(StatementDescriptor.class);
-        when(desc.getQueryDescriptor()).thenReturn(queryString);
+        when(desc.getDescriptor()).thenReturn(queryString);
         @SuppressWarnings("unchecked")
         Category<Pojo> mockCategory = (Category<Pojo>) mock(Category.class);
         when(desc.getCategory()).thenReturn(mockCategory);
@@ -138,6 +281,105 @@
         }
     }
     
+    private static class TestAdd implements Add<FooPojo> {
+
+        private Pojo pojo;
+        private boolean executed = false;
+        
+        @Override
+        public void setPojo(Pojo pojo) {
+            this.pojo = pojo;
+        }
+
+        @Override
+        public int apply() {
+            executed = true;
+            return 0;
+        }
+
+    }
+    
+    private static class TestReplace implements Replace<FooPojo> {
+
+        private Pojo pojo;
+        private boolean executed = false;
+        private Expression where;
+        
+        @Override
+        public void setPojo(Pojo pojo) {
+            this.pojo = pojo;
+        }
+
+        @Override
+        public void where(Expression expression) {
+            this.where = expression;
+        }
+
+        @Override
+        public int apply() {
+            this.executed = true;
+            return 0;
+        }
+        
+    }
+    
+    private static class TestUpdate implements Update<FooPojo> {
+
+        private Expression where;
+        private List<Pair<Key<Object>, Object>> updates = new ArrayList<>();
+        private boolean executed = false;
+        
+        @Override
+        public void where(Expression expr) {
+            this.where = expr;
+        }
+
+        @Override
+        public <S> void set(Key<S> key, S value) {
+            @SuppressWarnings("unchecked")
+            Pair<Key<Object>, Object> item = new Pair<>((Key<Object>) key, (Object)value);
+            updates.add(item);
+        }
+
+        @Override
+        public int apply() {
+            this.executed = true;
+            return 0;
+        }
+        
+    }
+    
+    private static class TestRemove implements Remove<FooPojo> {
+
+        private Expression where;
+        private boolean executed = false;
+        
+        @Override
+        public void where(Expression where) {
+            this.where = where;
+        }
+
+        @Override
+        public int apply() {
+            this.executed = true;
+            return 0;
+        }
+        
+    }
+    
+    public static class FooPojo implements Pojo {
+        
+        String foo;
+        
+        public void setFoo(String foo) {
+            this.foo = foo;
+        }
+        
+        public String getFoo() {
+            return this.foo;
+        }
+    }
+    
     private static class StubQuery implements Query<Pojo> {
 
         private Expression expr;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/SetListValueTest.java	Tue Sep 03 14:19:10 2013 +0200
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.internal.statement;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class SetListValueTest {
+
+    @Test
+    public void testEqualsHashCodeEmpty() {
+        SetListValue value = new SetListValue();
+        assertTrue(value.equals(value));
+        SetListValue value2 = new SetListValue();
+        assertTrue(value.equals(value2));
+        assertEquals(value.hashCode(), value2.hashCode());
+    }
+    
+    @Test
+    public void testEqualsHashCodeNonEmpty() {
+        TerminalNode keyNode = new TerminalNode(null);
+        keyNode.setValue("foo");
+        TerminalNode valNode = new TerminalNode(null);
+        valNode.setValue("bar-val");
+        SetListValue value = new SetListValue();
+        value.setKey(keyNode);
+        value.setValue(valNode);
+        assertTrue(value.equals(value));
+        SetListValue value2 = new SetListValue();
+        TerminalNode keyNode2 = new TerminalNode(null);
+        keyNode2.setValue("foo");
+        TerminalNode valNode2 = new TerminalNode(null);
+        valNode2.setValue("bar-val");
+        value2.setKey(keyNode2);
+        value2.setValue(valNode2);
+        assertEquals(valNode, valNode2);
+        assertEquals(keyNode, keyNode2);
+        assertTrue(value.equals(value2));
+        assertEquals(value.hashCode(), value2.hashCode());
+        assertFalse(value.equals("something-else"));
+    }
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/statement/StatementDescriptorParserTest.java	Tue Sep 03 14:19:10 2013 +0200
@@ -41,7 +41,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -53,32 +52,23 @@
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
+import com.redhat.thermostat.storage.core.Add;
 import com.redhat.thermostat.storage.core.AggregateQuery;
+import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction;
 import com.redhat.thermostat.storage.core.BackingStorage;
 import com.redhat.thermostat.storage.core.Category;
 import com.redhat.thermostat.storage.core.CategoryAdapter;
 import com.redhat.thermostat.storage.core.DescriptorParsingException;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.AggregateQuery.AggregateFunction;
 import com.redhat.thermostat.storage.core.Query.SortDirection;
+import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.Replace;
 import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.Update;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.model.AgentInformation;
 import com.redhat.thermostat.storage.model.AggregateCount;
-import com.redhat.thermostat.storage.internal.statement.BinaryExpressionNode;
-import com.redhat.thermostat.storage.internal.statement.LimitExpression;
-import com.redhat.thermostat.storage.internal.statement.NotBooleanExpressionNode;
-import com.redhat.thermostat.storage.internal.statement.ParsedStatementImpl;
-import com.redhat.thermostat.storage.internal.statement.SortExpression;
-import com.redhat.thermostat.storage.internal.statement.SortMember;
-import com.redhat.thermostat.storage.internal.statement.StatementDescriptorParser;
-import com.redhat.thermostat.storage.internal.statement.SuffixExpression;
-import com.redhat.thermostat.storage.internal.statement.TerminalNode;
-import com.redhat.thermostat.storage.internal.statement.UnfinishedLimitValue;
-import com.redhat.thermostat.storage.internal.statement.UnfinishedSortKey;
-import com.redhat.thermostat.storage.internal.statement.UnfinishedValueNode;
-import com.redhat.thermostat.storage.internal.statement.WhereExpression;
 import com.redhat.thermostat.storage.query.BinaryComparisonOperator;
 import com.redhat.thermostat.storage.query.BinaryLogicalOperator;
 
@@ -87,19 +77,39 @@
     private BackingStorage storage;
     private Query<AgentInformation> mockQuery;
     private StatementDescriptorParser<AgentInformation> parser;
+    private Add<AgentInformation> mockAdd;
+    private Update<AgentInformation> mockUpdate;
+    private Replace<AgentInformation> mockReplace;
+    private Remove<AgentInformation> mockRemove;
     
     @SuppressWarnings("unchecked")
     @Before
     public void setup() {
         storage = mock(BackingStorage.class);
         mockQuery = mock(Query.class);
-        when(storage.createQuery(any(AgentInfoDAO.CATEGORY.getClass()))).thenReturn(mockQuery);
+        when(storage.createQuery(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockQuery);
+        // setup for ADD
+        mockAdd = mock(Add.class);
+        when(storage.createAdd(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockAdd);
+        // setup for UPDATE
+        mockUpdate = mock(Update.class);
+        when(storage.createUpdate(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockUpdate);
+        // setup for REMOVE
+        mockRemove = mock(Remove.class);
+        when(storage.createRemove(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockRemove);
+        // setup for REPLACE
+        mockReplace = mock(Replace.class);
+        when(storage.createReplace(eq(AgentInfoDAO.CATEGORY))).thenReturn(mockReplace);
     }
     
     @After
     public void teardown() {
         storage = null;
         mockQuery = null;
+        mockUpdate = null;
+        mockReplace = null;
+        mockAdd = null;
+        mockRemove = null;
     }
     
     @SuppressWarnings({ "rawtypes", "unchecked" })
@@ -1270,6 +1280,124 @@
     }
     
     @Test
+    public void testParseAddBasic() throws DescriptorParsingException {
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = ?s";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertTrue(statement.getRawStatement() instanceof Add);
+        SuffixExpression suffix = statement.getSuffixExpression();
+        assertNotNull(suffix);
+        WhereExpression where = suffix.getWhereExpn();
+        LimitExpression limit = suffix.getLimitExpn();
+        SortExpression sort = suffix.getSortExpn();
+        assertNull(where);
+        assertNull(limit);
+        assertNull(sort);
+        SetList setList = statement.getSetList();
+        assertNotNull(setList);
+        List<SetListValue> valuesList = setList.getValues();
+        assertEquals(2, valuesList.size());
+        SetListValue first = valuesList.get(0);
+        SetListValue second = valuesList.get(1);
+        TerminalNode a = new TerminalNode(null);
+        a.setValue(new Key<>("a"));
+        TerminalNode b = new TerminalNode(null);
+        b.setValue("b");
+        SetListValue firstExpected = new SetListValue();
+        firstExpected.setKey(a);
+        firstExpected.setValue(b);
+        assertEquals(firstExpected, first);
+        TerminalNode c = new TerminalNode(null);
+        c.setValue(new Key<>("c"));
+        UnfinishedValueNode dVal = new UnfinishedValueNode();
+        dVal.setType(String.class);
+        dVal.setLHS(false);
+        dVal.setParameterIndex(0);
+        TerminalNode d = new TerminalNode(null);
+        d.setValue(dVal);
+        SetListValue secondExpected = new SetListValue();
+        secondExpected.setKey(c);
+        secondExpected.setValue(d);
+        assertEquals(secondExpected, second);
+    }
+    
+    @Test
+    public void testParseUpdateBasic() throws DescriptorParsingException {
+        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = ?s WHERE 'foo' != ?i";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        ParsedStatementImpl<AgentInformation> statement = null; 
+        try {
+            statement = (ParsedStatementImpl<AgentInformation>)parser.parse();
+        } catch (DescriptorParsingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+        assertTrue(statement.getRawStatement() instanceof Update);
+        SuffixExpression suffix = statement.getSuffixExpression();
+        assertNotNull(suffix);
+        WhereExpression where = suffix.getWhereExpn();
+        LimitExpression limit = suffix.getLimitExpn();
+        SortExpression sort = suffix.getSortExpn();
+        assertNotNull(where);
+        assertNull(limit);
+        assertNull(sort);
+        SetList setList = statement.getSetList();
+        assertNotNull(setList);
+        List<SetListValue> valuesList = setList.getValues();
+        assertEquals(2, valuesList.size());
+        SetListValue first = valuesList.get(0);
+        SetListValue second = valuesList.get(1);
+        TerminalNode a = new TerminalNode(null);
+        a.setValue(new Key<>("a"));
+        TerminalNode b = new TerminalNode(null);
+        b.setValue("b");
+        SetListValue firstExpected = new SetListValue();
+        firstExpected.setKey(a);
+        firstExpected.setValue(b);
+        assertEquals(firstExpected, first);
+        TerminalNode c = new TerminalNode(null);
+        c.setValue(new Key<>("c"));
+        UnfinishedValueNode dVal = new UnfinishedValueNode();
+        dVal.setType(String.class);
+        dVal.setLHS(false);
+        dVal.setParameterIndex(0);
+        TerminalNode d = new TerminalNode(null);
+        d.setValue(dVal);
+        SetListValue secondExpected = new SetListValue();
+        secondExpected.setKey(c);
+        secondExpected.setValue(d);
+        assertEquals(secondExpected, second);
+        // Build expected where expn
+        WhereExpression expectedWhere = new WhereExpression();
+        BinaryExpressionNode equality = new BinaryExpressionNode(expectedWhere.getRoot());
+        expectedWhere.getRoot().setValue(equality);
+        equality.setOperator(BinaryComparisonOperator.NOT_EQUAL_TO);
+        TerminalNode foo = new TerminalNode(equality);
+        @SuppressWarnings("rawtypes")
+        Key fooKey = new Key("foo");
+        foo.setValue(fooKey);
+        equality.setLeftChild(foo);
+        TerminalNode fooVal = new TerminalNode(equality);
+        UnfinishedValueNode node = new UnfinishedValueNode();
+        node.setParameterIndex(1);
+        node.setLHS(false);
+        node.setType(Integer.class);
+        fooVal.setValue(node);
+        equality.setRightChild(fooVal);
+        assertTrue(WhereExpressions.equals(expectedWhere, where));
+    }
+    
+    // TODO: Add basic parse tests for REMOVE/REPLACE
+    
+    @Test
     public void rejectLongValAsIntType() throws DescriptorParsingException {
         // 30000000003 > Integer.MAX_VALUE; needs to be preceded by 'l/L'
         String descrString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' != 30000000003";
@@ -1482,4 +1610,153 @@
         }
     }
     
+    @Test
+    public void rejectAddWithInvalidSetList() throws DescriptorParsingException {
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = , 'c' = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("Invalid SET values list.");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertEquals("Illegal terminal type. Token was ->,<-", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void rejectAddWithWhere() throws DescriptorParsingException {
+        String descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd' WHERE 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("ADD operation does not support WHERE clauses!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("WHERE clause not allowed for ADD"));
+        }
+    }
+    
+    @Test
+    public void rejectReplaceWithoutWhere() throws DescriptorParsingException {
+        String descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("REPLACE operation requires WHERE clause, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("WHERE clause required for REPLACE"));
+        }
+    }
+    
+    @Test
+    public void rejectUpdateWithoutWhere() throws DescriptorParsingException {
+        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' , 'c' = 'd'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("UPDATE operation requires WHERE clause, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("WHERE clause required for UPDATE"));
+        }
+    }
+    
+    @Test
+    public void rejectRemoveWithSetList() throws DescriptorParsingException {
+        String descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("REMOVE does not allow SET list, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("SET not allowed for REMOVE"));
+        }
+    }
+    
+    @Test
+    public void rejectQueryWithSetList() throws DescriptorParsingException {
+        String descString = "QUERY " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("QUERY does not allow SET list, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertTrue(e.getMessage().contains("SET not allowed for QUERY"));
+        }
+    }
+    
+    
+    private void doRejectWriteSortLimitTest(String descString, String failmsg) {
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail(failmsg);
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertEquals("LIMIT/SORT only allowed for QUERY/QUERY-COUNT", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void rejectWritesWithSortLimit() throws DescriptorParsingException {
+        // Update rejects
+        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' SORT 'a' DSC";
+        String failmsg = "SORT in UPDATE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' LIMIT 1";
+        failmsg = "LIMIT in UPDATE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        
+        // Remove rejects
+        descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' LIMIT 1";
+        failmsg = "LIMIT in REMOVE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        descString = "REMOVE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b' SORT 'a' ASC";
+        failmsg = "SORT in REMOVE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        
+        // Replace rejects
+        descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' LIMIT 1";
+        failmsg = "LIMIT in REPLACE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        descString = "REPLACE " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' WHERE 'a' = 'b' SORT 'a' ASC";
+        failmsg = "SORT in REPLACE is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        
+        // Add rejects
+        descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' LIMIT 1";
+        failmsg = "LIMIT in ADD is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+        descString = "ADD " + AgentInfoDAO.CATEGORY.getName() + " SET 'a' = 'b' SORT 'a' ASC";
+        failmsg = "SORT in ADD is not allowed, and should not parse!";
+        doRejectWriteSortLimitTest(descString, failmsg);
+    }
+    
+    @Test
+    public void rejectUpdateWithoutSet() throws DescriptorParsingException {
+        String descString = "UPDATE " + AgentInfoDAO.CATEGORY.getName() + " WHERE 'a' = 'b'";
+        StatementDescriptor<AgentInformation> desc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, descString);
+        parser = new StatementDescriptorParser<>(storage, desc);
+        try {
+            parser.parse();
+            fail("UPDATE requires SET list, and should not parse!");
+        } catch (DescriptorParsingException e) {
+            // pass
+            assertEquals("SET list required for UPDATE", e.getMessage());
+        }
+    }
+    
+    // TODO: add tests where set list does not match all props of Pojo class for
+    // add/replace
+    
 }
--- a/storage/mongo/pom.xml	Mon Sep 30 16:38:50 2013 +0200
+++ b/storage/mongo/pom.xml	Tue Sep 03 14:19:10 2013 +0200
@@ -91,18 +91,6 @@
       <artifactId>mongo-java-driver</artifactId>
     </dependency>
     <dependency>
-      <groupId>commons-beanutils</groupId>
-      <artifactId>commons-beanutils</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>commons-collections</groupId>
-      <artifactId>commons-collections</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>commons-logging</groupId>
-      <artifactId>commons-logging</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.powermock</groupId>
       <artifactId>powermock-api-mockito</artifactId>
       <scope>test</scope>
--- a/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/web/client/src/main/java/com/redhat/thermostat/web/client/internal/WebStorage.java	Tue Sep 03 14:19:10 2013 +0200
@@ -770,7 +770,7 @@
     @Override
     public <T extends Pojo> PreparedStatement<T> prepareStatement(StatementDescriptor<T> desc)
             throws DescriptorParsingException {
-        String strDesc = desc.getQueryDescriptor();
+        String strDesc = desc.getDescriptor();
         int categoryId = getCategoryId(desc.getCategory());
         NameValuePair nameParam = new BasicNameValuePair("query-descriptor",
                 strDesc);
@@ -788,7 +788,7 @@
                 // we've got a descriptor the endpoint doesn't know about or
                 // refuses to accept for security reasons.
                 String msg = "Unknown query descriptor which endpoint of " + WebStorage.class.getName() + " refused to accept!";
-                throw new IllegalDescriptorException(msg, desc.getQueryDescriptor());
+                throw new IllegalDescriptorException(msg, desc.getDescriptor());
             } else if (statementId == WebPreparedStatementResponse.DESCRIPTOR_PARSE_FAILED) {
                 String msg = "Statement descriptor failed to parse. " +
                              "Please check server logs for details!";
--- a/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Mon Sep 30 16:38:50 2013 +0200
+++ b/web/server/src/main/java/com/redhat/thermostat/web/server/WebStorageEndPoint.java	Tue Sep 03 14:19:10 2013 +0200
@@ -325,9 +325,9 @@
         }
         StatementDescriptor<T> desc = new StatementDescriptor<>(cat, queryDescrParam);
         // Check if descriptor is trusted (i.e. known)
-        if (!knownStatementDescriptors.contains(desc.getQueryDescriptor())) {
+        if (!knownStatementDescriptors.contains(desc.getDescriptor())) {
             String msg = "Attempted to prepare a statement descriptor which we " +
-            		"don't trust! Descriptor was: ->" + desc.getQueryDescriptor() + "<-";
+            		"don't trust! Descriptor was: ->" + desc.getDescriptor() + "<-";
             logger.log(Level.WARNING, msg);
             response.setStatementId(WebPreparedStatementResponse.ILLEGAL_STATEMENT);
             writeResponse(resp, response, WebPreparedStatementResponse.class);
@@ -682,8 +682,8 @@
         }
         
         StatementDescriptor<T> desc = targetStmtHolder.getStatementDescriptor();
-        StatementDescriptorMetadataFactory factory = descMetadataFactories.get(desc.getQueryDescriptor());
-        DescriptorMetadata actualMetadata = factory.getDescriptorMetadata(desc.getQueryDescriptor(), params);
+        StatementDescriptorMetadataFactory factory = descMetadataFactories.get(desc.getDescriptor());
+        DescriptorMetadata actualMetadata = factory.getDescriptorMetadata(desc.getDescriptor(), params);
         
         UserPrincipal userPrincipal = getUserPrincipal(req);
         targetQuery = getQueryForPrincipal(userPrincipal, targetQuery, desc, actualMetadata);