/* XXL: The eXtensible and fleXible Library for data processing Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger Head of the Database Research Group Department of Mathematics and Computer Science University of Marburg Germany This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; If not, see <http://www.gnu.org/licenses/>. http://code.google.com/p/xxl/ */ package xxl.core.xxql; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import xxl.core.collections.MapEntry; import xxl.core.predicates.Predicate; import xxl.core.relational.tuples.Tuple; import xxl.core.xxql.AdvTupleCursor.CachingStrategy; import xxl.core.xxql.columns.Column; /** * A (AdvResultSet)MetaData-aware Predicate on Tuples to be used mainly in JOINS and WHERE-clauses * of the {@link AdvTupleCursor}.<br> * * @see Column */ public abstract class AdvPredicate implements Predicate<Tuple>, CorrTuplesReceiver { // Columns "used" by this predicate protected List<Column> containedColumns = null; // AdvPredicates "used" by this predicate (like in AND) protected List<AdvPredicate> containedPredicates = null; // correlated tuples (i.e. if this is a subquery the current tuple of the // enclosing WHERE-clause - in case of nested subqueries there are several enclosing // WHERE-clausis with current tuples, so it's a list protected List<MapEntry<AdvResultSetMetaData, Tuple>> corr_tuples=null; // the metadata of the cursor containing this predicate. protected AdvResultSetMetaData metadata; /** * Add columns "contained" in this predicate so metadata and correlated tuples can be set for them.<br> * Contained Columns are Columns needed to evaluate this Predicate<br> * Example:<br> * <code>col("al1.nameX").EQ(col("al2.nameY"))</code> <br>checks whether the columns "al1.nameX" and * "al2.nameY", that should be present in your Cursor or the correlated Tuples, are equal.<br> * To map those names to the correct column in the Cursor/Tuple, MetaData and the correlated * Tuples are needed. They will be set by where() via setMetaData() and setCorrelatedTuples() * of this AdvPredicate and setMetaData() will pass on the metadata to the contained Columns. * * @param cols Columns to be contained in this Column * * @see Column#setCorrelatedTuples(List) */ // TODO: reicht package protected? public void addContainedColumns(Column... cols){ if(containedColumns == null){ containedColumns = new ArrayList<Column>(cols.length); } for(Column col : cols){ containedColumns.add(col); } } /** * Add contained Predicates (like in AND(), OR()...) so metadata and correlated tuples will be * passed on to them (and they can pass it on to "their" columns) * * @param preds the AdvPredicates contained in this predicate */ private void addContainedPredicates(AdvPredicate ... preds){ if(containedPredicates == null){ containedPredicates = new ArrayList<AdvPredicate>(preds.length); } for(AdvPredicate pred : preds){ containedPredicates.add(pred); } } /** * Set the AdvResultSetMetaData of the cursor the contained columns (probably) belong to and * the new alias of the resulting cursor.<br> * If this is a <b>join</b> Predicate you will have to set the AdvResultSetMetaData of both * joined Cursors - see {@link #setMetaDatas(AdvResultSetMetaData, AdvResultSetMetaData)} * * @param metadata AdvResultSetMetaData of the cursor, you're calling * where/select/group_by on with this column * @param newAlias the new alias of the resulting cursor after where/select/group_by * (needed especially if this Column contains a given value from * {@link Column#val(Object, String) val(obj, name)} * * @see Column#setMetaData(AdvResultSetMetaData, String) * @see #setMetaDatas(AdvResultSetMetaData, AdvResultSetMetaData) */ // TODO: wuerde package protected reichen? public void setMetaData(AdvResultSetMetaData metadata, String newAlias) { // the predicate itself may need metadata in case of EXISTS etc this.metadata = metadata; // and it has to pass the metadata on to the contained columns and predicates. if(containedColumns != null) { for(Column col : containedColumns) col.setMetaData(metadata, newAlias); } if(containedPredicates != null) { for(AdvPredicate pred : containedPredicates) pred.setMetaData(metadata, newAlias); } } /** * When used as a JOIN-Predicate, set the {@link AdvResultSetMetaData}s of the the left and * right Cursors that are joined. It will be passed on to contained Columns, so they can map * their given "alias.name" to the appropriate cursor and choose the correct tuple when * invoked. * @param leftMetaData AdvResultSetMetaData of the left cursor being joined * @param rightMetaData AdvResultSetMetaData of the right cursor being joined */ public void setMetaDatas(AdvResultSetMetaData leftMetaData, AdvResultSetMetaData rightMetaData){ this.metadata = null; // should really not be needed in a join-predicate (don't want subqueries there) if(containedColumns != null) { for(Column col : containedColumns) col.setMetaDatas(leftMetaData, rightMetaData); } if(containedPredicates != null) { for(AdvPredicate pred : containedPredicates) pred.setMetaDatas(leftMetaData, rightMetaData); } } protected AdvResultSetMetaData getMetaData(){ return metadata; } /** * Pass a list of tuples along with their metadata from "outer" cursors in case this is a * (correlated) subquery. The list has to be sorted from inside to outside (in case of nested * subqueries). It will be passed on to the contained Columns.<br> * This is meant to be used with EXISTS, ALL and ANY clauses. * * @param corrTuples a List of tuples with their related metadata from "outer" cursors * for correlated subqueries * * @see AdvTupleCursor#getCorrTuples() * @see Column#setCorrelatedTuples(List) */ public void setCorrelatedTuples(List<MapEntry<AdvResultSetMetaData, Tuple>> corrTuples){ if (corrTuples == null) return; corr_tuples = corrTuples; if(containedColumns != null) { for(Column col : containedColumns) col.setCorrelatedTuples(corrTuples); } if(containedPredicates != null) { for(AdvPredicate pred : containedPredicates) pred.setCorrelatedTuples(corrTuples); } } protected List<MapEntry<AdvResultSetMetaData, Tuple>> getCorrelatedTuples(){ return corr_tuples; } /** * creates a logical AND connection between this Predicate and the other Predicate * @param other the other Predicate * @return true if <b>this</b> and <b>other</b> both evaluate to true, false otherwise */ public AdvPredicate AND(final AdvPredicate other){ AdvPredicate ret = new AdvPredicate(){ @Override public boolean invoke(Tuple tuple) { return AdvPredicate.this.invoke(tuple) && other.invoke(tuple); } @Override public boolean invoke(Tuple lTuple, Tuple rTuple) { return AdvPredicate.this.invoke(lTuple, rTuple) && other.invoke(lTuple, rTuple); } }; // add *this* and the other predicate so metadata and correlated tuples will be passed on // to them from ret ret.addContainedPredicates(this, other); return ret; } /** * creates a logical OR connection between this Predicate and the other Predicate * @param other the other Predicate * @return true if at least one of <b>this</b> or <b>other</b> evaluates to true, false otherwise */ public AdvPredicate OR(final AdvPredicate other){ AdvPredicate ret = new AdvPredicate(){ @Override public boolean invoke(Tuple tuple) { return AdvPredicate.this.invoke(tuple) || other.invoke(tuple); } @Override public boolean invoke(Tuple lTuple, Tuple rTuple) { return AdvPredicate.this.invoke(lTuple, rTuple) || other.invoke(lTuple, rTuple); } }; // add *this* and the other predicate so metadata and correlated tuples will be passed on // to them from ret ret.addContainedPredicates(this, other); return ret; } /** * creates a logical XOR connection between this Predicate and the other Predicate * @param other the other Predicate * @return true if <u>either</u> <b>this</b> <u>or</u> <b>other</b> (but not both of them) * evaluates to true, false otherwise. */ public AdvPredicate XOR(final AdvPredicate other){ AdvPredicate ret = new AdvPredicate(){ @Override public boolean invoke(Tuple tuple) { boolean first = AdvPredicate.this.invoke(tuple); boolean second = other.invoke(tuple); return (first || second) && !(first && second); } @Override public boolean invoke(Tuple lTuple, Tuple rTuple) { boolean first = AdvPredicate.this.invoke(lTuple, rTuple); boolean second = other.invoke(lTuple, rTuple); return (first || second) && !(first && second); } }; // add *this* and the other predicate so metadata and correlated tuples will be passed on // to them from ret ret.addContainedPredicates(this, other); return ret; } /** * Creates an AdvPredicate that negates the given AdvPredicate.<br> * <b>Example:</b> <br> * ...where( NOT(col("a1.blah").EQ(val("foo"))) )... * @param pred the predicate to negate * @return false if pred evaluates to true and vice versa */ public static AdvPredicate NOT(final AdvPredicate pred){ AdvPredicate ret = new AdvPredicate(){ @Override public boolean invoke(Tuple tuple) { return !pred.invoke(tuple); } @Override public boolean invoke(Tuple lTuple, Tuple rTuple) { return !pred.invoke(lTuple, rTuple); } }; // add pred to contained predicates so metadata and correlated tuples will be passed on // to them from ret ret.addContainedPredicates(pred); return ret; } /** * Creates an AdvPredicate that is true if the value is <i>null</i><br> * * @param col the Column to check for being null * @return true if col is evaluated to null, else false */ public static AdvPredicate ISNULL(final Column col){ AdvPredicate ret = new AdvPredicate(){ @Override public boolean invoke(Tuple tuple) { return col.invoke(tuple) == null; } @Override public boolean invoke(Tuple lTuple, Tuple rTuple) { return col.invoke(lTuple, rTuple) == null; } }; // add col to contained columns so metadata and correlated tuples will be passed on to it ret.addContainedColumns(col); return ret; } /** * Creates a EXISTS predicate with a (possibly correlated) subquery. If the subquery returns * any tuples (or just one), i.e. hasNext() returns <i>true</i>, the predicate returns true, * else (there does <i>not <b>exist</b></i> a single tuple in the subquery) it returns false. * <br><b>This may not be used as a join Predicate!</b><br> * <b>Example:</b><br><pre> * <code>// some cursors * AdvTupleCursor cur1 = new AdvTupleCursor(someList, "cur1"); * AdvTupleCursor cur2 = new AdvTupleCursor(someOtherList, "cur2"); * AdvTupleCursor res = cur1.where( * EXISTS( cur2.where( col("cur2.a").EQ(col("cur1.x")) ) ) * );</code></pre> * This cursor returns all tuples from cur1 with a value in their Column named "x" that * is contained in at least one tuple from cur2 at its column named "a".<br> * <i>Note:</i> <ul><li> Because the subquery needs to be re-evaluated for each new "outer" tuple from * res' WHERE clause, it can not be cached. Only the first cursor in that query (cur2) will be * cached to ensure that it supports reset() (it will be resetted for each "outer" tuple).</li> * <li>Subqueries are not really efficient and normally can easily be replaced by a join.</li></ul> * @param subquery The subquery evaluated in the EXISTS clause * @return an AdvPredicate implementing an EXISTS-subquery */ public static AdvPredicate EXISTS(final AdvTupleCursor subquery){ // subquery should not cache - except for the first cursor, so it's resettable. // so if "only cache if absolutely necessary" via MAYBE_FIRST isn't enforced, we'll enable // caching the first cursor (even if the wrapped cursor is resettable) if(!subquery.getCachingStrategy().equals(CachingStrategy.MAYBE_FIRST) && !subquery.getCachingStrategy().equals(CachingStrategy.ONLY_FIRST)) subquery.setCachingStrategy(CachingStrategy.ONLY_FIRST, true); AdvPredicate ret = new AdvPredicate() { @Override public boolean invoke(Tuple tuple) { boolean result; // current correlated tuple for subquery MapEntry<AdvResultSetMetaData, Tuple> newCorrTuple = new MapEntry<AdvResultSetMetaData, Tuple>(getMetaData(), tuple); // create list of correlated Tuples ("this" tuple and the ones "inherited" from the containing cursor) List<MapEntry<AdvResultSetMetaData, Tuple>> corrTuples = new LinkedList<MapEntry<AdvResultSetMetaData,Tuple>>(); corrTuples.add(newCorrTuple); // current tuple if(getCorrelatedTuples() != null) corrTuples.addAll(getCorrelatedTuples()); // tuples that were set in this predicate via setCorrelatedTuples() // set correlated tuples in subquery's CorrTuplesReceivers (predicates, ..) for(CorrTuplesReceiver ctr : subquery.getCorrTuplesRec()) { ctr.setCorrelatedTuples(corrTuples); } // if the subquery contains at least one tuple, exists is true. result = subquery.hasNext(); // reset subquery for next tuple in where.. subquery.reset(); return result; } @Override public boolean invoke(Tuple argument0, Tuple argument1) { throw new UnsupportedOperationException("Don't use EXISTS in a JOIN-predicate."); } }; return ret; } /** * An AdvPredicate that always returns <i>false</i>, regardless of the input tuples.<br> * Probably mostly useful for testing purposes. */ public static AdvPredicate FALSE = new AdvPredicate() { @Override public boolean invoke(Tuple argument0, Tuple argument1) { return false; } @Override public boolean invoke(Tuple argument) { return false; } }; /** * An AdvPredicate that always returns <i>true</i>, regardless of the input tuples.<br> * Useful to make an equi-join behave like a cross product or for testing purposes. */ public static AdvPredicate TRUE = new AdvPredicate() { @Override public boolean invoke(Tuple argument0, Tuple argument1) { return true; } @Override public boolean invoke(Tuple argument) { return true; } }; /*********************************************************************** * "clean" versions of the methods * * (the ones not needed throw exceptions, the other ones are abstract) * ***********************************************************************/ @Override // this method isn't used anyway public boolean invoke(List<? extends Tuple> arguments) { throw new UnsupportedOperationException("not implemented"); } @Override // this method isn't used anyway public boolean invoke() { throw new UnsupportedOperationException("not implemented"); } /** * Invokes this Predicate on the given Tuple. The standard use case for this are WHERE-clauses: * If the given Predicate evaluates to true, the tuple will be the next tuple returned by * next(), if it evaluates to false, the tuple will be discarded. * <b>A simple Example:</b> <code>AdvTupleCursor res = cur1.where(col("a").LEQ(val("42")))</code><br> * LEQ returns an AdvPredicate that compares the given Columns and returns true if the first * Column (col("a")) is less or equals the second Column(val("42")), so only Tuples with a * value of 42 or smaller in their Column named "a" will be returned by the where-operator. * @param argument the tuple to be evaluated */ @Override public abstract boolean invoke(Tuple argument); /** * Invokes this Predicate on the given Tuples. The standard use case for this are * JOIN predicates. If the Predicates evaluates to true, the tuples will be concatenated * and returned by the join-operator, if it evaluates to false, that combination of tuples * will be ignored.<br> * <b>A simple Example:</b> <code>AdvTupleCursor res = cur1.join(cur2, col("cur1.a").EQ(col("cur2.x"))); * </code><br> EQ returns a Predicate that compares the given Columns, in this case column "a" * from leftTuple (because leftTuple is from cur1) and column "b" from rightTuple, and if these * Columns are equal, leftTuple and rightTuple will be * {@link AdvTupleCursor#concatTuples(Tuple, Tuple, int, int) concatenated} and returned by the * cursor res.<br> * @param leftTuple tuple of the left cursor being joined (cur1 in example) * @param rightTuple tuple of the right cursor being joined (cur2 in example) */ @Override public abstract boolean invoke(Tuple leftTuple, Tuple rightTuple); }