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