package edu.caltech.nanodb.expressions; import java.util.ArrayList; import java.util.List; import edu.caltech.nanodb.queryast.SelectClause; import edu.caltech.nanodb.relations.ColumnInfo; import edu.caltech.nanodb.relations.ColumnType; import edu.caltech.nanodb.relations.Schema; import edu.caltech.nanodb.relations.SchemaNameException; import edu.caltech.nanodb.relations.Tuple; /** * This class implements the <tt>expr IN (subquery)</tt> operator. This * operation may be optimized out of a query, but if it is not, it can still * be evaluated although it will be slow. */ public class InSubqueryOperator extends SubqueryOperator { /** * The list of expressions to check against the set on the righthand side of the * <tt>IN</tt> operator. */ private ArrayList<Expression> exprList = new ArrayList<>(); /** * If this is false, the operator computes <tt>expr IN (subquery)</tt>; * if true, the operator computes <tt>expr NOT IN (subquery)</tt>. */ private boolean invert = false; public InSubqueryOperator(Expression expr, SelectClause subquery) { if (expr == null) throw new IllegalArgumentException("expr cannot be null"); if (subquery == null) throw new IllegalArgumentException("subquery cannot be null"); exprList.add(expr); this.subquery = subquery; } public InSubqueryOperator(List<Expression> exprList, SelectClause subquery) { if (exprList == null) throw new IllegalArgumentException("exprList cannot be null"); if (exprList.isEmpty()) throw new IllegalArgumentException("exprList cannot be empty"); if (subquery == null) throw new IllegalArgumentException("subquery cannot be null"); this.exprList.addAll(exprList); this.subquery = subquery; } public void setInvert(boolean invert) { this.invert = invert; } public ColumnInfo getColumnInfo(Schema schema) throws SchemaNameException { // Comparisons always return Boolean values, so just pass a Boolean // value in to the TypeConverter to get out the corresponding SQL type. ColumnType colType = new ColumnType(TypeConverter.getSQLType(Boolean.FALSE)); return new ColumnInfo(colType); } /** * Evaluates this comparison expression and returns either * {@link java.lang.Boolean#TRUE} or {@link java.lang.Boolean#FALSE}. If * either the left-hand or right-hand expression evaluates to * <code>null</code> (representing the SQL <tt>NULL</tt> value), then the * expression's result is always <code>FALSE</code>. * * @design (Donnie) We have to suppress "unchecked operation" warnings on * this code, since {@link Comparable} is a generic (and thus allows * us to specify the type of object being compared), but we want to * use it without specifying any types. */ @SuppressWarnings("unchecked") public Object evaluate(Environment env) throws ExpressionException { if (subqueryPlan == null) throw new IllegalStateException("No execution plan for subquery"); // Compute the values that we need to compare to, into a tuple. Then // we can use the tuple-comparator to see if the subquery produces the // same tuple-values. TupleLiteral valueTup = new TupleLiteral(); for (Expression expr : exprList) { // TODO: Return NULL if one of the values is NULL? valueTup.addValue(expr.evaluate(env)); } subqueryPlan.initialize(); while (true) { Tuple subqueryTup = subqueryPlan.getNextTuple(); if (subqueryTup == null) break; if (TupleComparator.areTuplesEqual(valueTup, subqueryTup)) return invert ? false : true; } return invert ? true : false; } @Override public Expression traverse(ExpressionProcessor p) { p.enter(this); for (int i = 0; i < exprList.size(); i++) exprList.set(i, exprList.get(i).traverse(p)); // We do not traverse the subquery; it is treated as a "black box" // by the expression-traversal mechanism. return p.leave(this); } /** * Returns a string representation of this comparison expression and its * subexpressions. */ @Override public String toString() { StringBuilder buf = new StringBuilder(); // Convert all of the components into string representations. if (exprList.size() == 1) { buf.append(exprList.get(0).toString()); } else { char ch = '('; for (Expression expr : exprList) { buf.append(ch); ch = ','; buf.append(expr.toString()); } buf.append(')'); } if (invert) buf.append(" NOT"); buf.append(" IN ("); buf.append(subquery.toString()); buf.append(')'); return buf.toString(); } /** * Checks if the argument is an expression with the same structure, but not * necessarily the same references. * * @param obj the object to which we are comparing */ @Override public boolean equals(Object obj) { if (obj instanceof InSubqueryOperator) { InSubqueryOperator other = (InSubqueryOperator) obj; return exprList.equals(other.exprList) && subquery.equals(other.subquery); } return false; } @Override public int hashCode() { int hash = 7; for (Expression expr : exprList) hash = 31 * hash + expr.hashCode(); hash = 31 * hash + subquery.hashCode(); return hash; } /** * Creates a copy of expression. This method is used by the * {@link Expression#duplicate} method to make a deep copy of an expression * tree. */ @Override @SuppressWarnings("unchecked") protected Object clone() throws CloneNotSupportedException { InSubqueryOperator op = (InSubqueryOperator) super.clone(); // Clone the subexpressions. op.exprList = (ArrayList<Expression>) exprList.clone(); // Don't clone the subquery, since subqueries currently aren't cloneable. return op; } }