001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2006-2011 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.rolap.agg;
011
012import mondrian.olap.Util;
013import mondrian.rolap.*;
014import mondrian.rolap.sql.SqlQuery;
015
016import java.util.*;
017
018/**
019 * Predicate which is the union of a list of predicates, each of which applies
020 * to the same, single column. It evaluates to
021 * true if any of the predicates evaluates to true.
022 *
023 * @see mondrian.rolap.agg.ListColumnPredicate
024 *
025 * @author jhyde
026 * @since Nov 2, 2006
027 */
028public class ListColumnPredicate extends AbstractColumnPredicate {
029    /**
030     * List of column predicates.
031     */
032    private final List<StarColumnPredicate> children;
033
034    /**
035     * Hash map of children predicates, keyed off of the hash code of each
036     * child.  Each entry in the map is a list of predicates matching that
037     * hash code.
038     */
039    private HashMap<Integer, List<StarColumnPredicate>> childrenHashMap;
040
041    /**
042     * Set of child values, if all child predicates are value predicates; null
043     * otherwise.
044     */
045    private final Set<Object> values;
046
047    /**
048     * Pre-computed hash code for this list column predicate
049     */
050    private int hashValue;
051
052    /**
053     * Creates a ListColumnPredicate
054     *
055     * @param column Column being constrained
056     * @param list List of child predicates
057     */
058    public ListColumnPredicate(
059        RolapStar.Column column,
060        List<StarColumnPredicate> list)
061    {
062        super(column);
063        this.children = list;
064        childrenHashMap = null;
065        hashValue = 0;
066        values = createValues(list);
067    }
068
069    private static Set<Object> createValues(List<StarColumnPredicate> list) {
070        final HashSet<Object> set = new HashSet<Object>();
071        for (StarColumnPredicate predicate : list) {
072            if (predicate instanceof ValueColumnPredicate) {
073                set.add(((ValueColumnPredicate) predicate).getValue());
074            } else {
075                // One of the children is not a value predicate. We will have to
076                // evaluate the predicate long-hand.
077                return null;
078            }
079        }
080        return set;
081    }
082
083    /**
084     * Returns the list of child predicates.
085     *
086     * @return list of child predicates
087     */
088    public List<StarColumnPredicate> getPredicates() {
089        return children;
090    }
091
092    public int hashCode() {
093        // Don't use the default list hashcode because we want a hash code
094        // that's not order dependent
095        if (hashValue == 0) {
096            hashValue = 37;
097            for (StarColumnPredicate child : children) {
098                int childHashCode = child.hashCode();
099                if (childHashCode != 0) {
100                    hashValue *= childHashCode;
101                }
102            }
103            hashValue ^= children.size();
104        }
105        return hashValue;
106    }
107
108    public boolean equals(Object obj) {
109        if (obj instanceof ListColumnPredicate) {
110            ListColumnPredicate that = (ListColumnPredicate) obj;
111            return this.children.equals(that.children);
112        } else {
113            return false;
114        }
115    }
116
117    public void values(Collection<Object> collection) {
118        if (values != null) {
119            collection.addAll(values);
120        } else {
121            for (StarColumnPredicate child : children) {
122                child.values(collection);
123            }
124        }
125    }
126
127    public boolean evaluate(Object value) {
128        for (StarColumnPredicate childPredicate : children) {
129            if (childPredicate.evaluate(value)) {
130                return true;
131            }
132        }
133        return false;
134    }
135
136    public boolean equalConstraint(StarPredicate that) {
137        boolean isEqual =
138            that instanceof ListColumnPredicate
139            && getConstrainedColumnBitKey().equals(
140                that.getConstrainedColumnBitKey());
141
142        if (isEqual) {
143            ListColumnPredicate thatPred = (ListColumnPredicate) that;
144            if (getPredicates().size() != thatPred.getPredicates().size()) {
145                isEqual = false;
146            } else {
147                // Create a hash map of the children predicates, if not
148                // already done
149                if (childrenHashMap == null) {
150                    childrenHashMap =
151                        new HashMap<Integer, List<StarColumnPredicate>>();
152                    for (StarColumnPredicate thisChild : getPredicates()) {
153                        Integer key = thisChild.hashCode();
154                        List<StarColumnPredicate> predList =
155                            childrenHashMap.get(key);
156                        if (predList == null) {
157                            predList = new ArrayList<StarColumnPredicate>();
158                            childrenHashMap.put(key, predList);
159                        }
160                        predList.add(thisChild);
161                    }
162                }
163
164                // Loop through thatPred's children predicates.  There needs
165                // to be a matching entry in the hash map for each child
166                // predicate.
167                for (StarColumnPredicate thatChild : thatPred.getPredicates()) {
168                    List<StarColumnPredicate> predList =
169                        childrenHashMap.get(thatChild.hashCode());
170                    if (predList == null) {
171                        isEqual = false;
172                        break;
173                    }
174                    boolean foundMatch = false;
175                    for (StarColumnPredicate pred : predList) {
176                        if (thatChild.equalConstraint(pred)) {
177                            foundMatch = true;
178                            break;
179                        }
180                    }
181                    if (!foundMatch) {
182                        isEqual = false;
183                        break;
184                    }
185                }
186            }
187        }
188        return isEqual;
189    }
190
191    public void describe(StringBuilder buf) {
192        buf.append("={");
193        for (int j = 0; j < children.size(); j++) {
194            if (j > 0) {
195                buf.append(", ");
196            }
197            buf.append(children.get(j));
198        }
199        buf.append('}');
200    }
201
202    public Overlap intersect(StarColumnPredicate predicate) {
203        int matchCount = 0;
204        for (StarColumnPredicate flushPredicate : children) {
205            final Overlap r2 = flushPredicate.intersect(predicate);
206            if (r2.matched) {
207                // A hit!
208                if (r2.remaining == null) {
209                    // Total match.
210                    return r2;
211                } else {
212                    // Partial match.
213                    predicate = r2.remaining;
214                    ++matchCount;
215                }
216            }
217        }
218        if (matchCount == 0) {
219            return new Overlap(false, null, 0f);
220        } else {
221            float selectivity =
222                (float) matchCount
223                / (float) children.size();
224            return new Overlap(true, predicate, selectivity);
225        }
226    }
227
228    public boolean mightIntersect(StarPredicate other) {
229        if (other instanceof LiteralStarPredicate) {
230            return ((LiteralStarPredicate) other).getValue();
231        }
232        if (other instanceof ValueColumnPredicate) {
233            ValueColumnPredicate valueColumnPredicate =
234                (ValueColumnPredicate) other;
235            return evaluate(valueColumnPredicate.getValue());
236        }
237        if (other instanceof ListColumnPredicate) {
238            final List<Object> thatSet = new ArrayList<Object>();
239            ((ListColumnPredicate) other).values(thatSet);
240            for (Object o : thatSet) {
241                if (evaluate(o)) {
242                    return true;
243                }
244            }
245            return false;
246        }
247        throw Util.newInternal("unknown constraint type " + other);
248    }
249
250    public StarColumnPredicate minus(StarPredicate predicate) {
251        assert predicate != null;
252        if (predicate instanceof LiteralStarPredicate) {
253            LiteralStarPredicate literalStarPredicate =
254                (LiteralStarPredicate) predicate;
255            if (literalStarPredicate.getValue()) {
256                // X minus TRUE --> FALSE
257                return LiteralStarPredicate.FALSE;
258            } else {
259                // X minus FALSE --> X
260                return this;
261            }
262        }
263        StarColumnPredicate columnPredicate = (StarColumnPredicate) predicate;
264        List<StarColumnPredicate> newChildren =
265            new ArrayList<StarColumnPredicate>(children);
266        int changeCount = 0;
267        final Iterator<StarColumnPredicate> iterator = newChildren.iterator();
268        while (iterator.hasNext()) {
269            ValueColumnPredicate child =
270                (ValueColumnPredicate) iterator.next();
271            if (columnPredicate.evaluate(child.getValue())) {
272                ++changeCount;
273                iterator.remove();
274            }
275        }
276        if (changeCount > 0) {
277            return new ListColumnPredicate(getConstrainedColumn(), newChildren);
278        } else {
279            return this;
280        }
281    }
282
283    public StarColumnPredicate orColumn(StarColumnPredicate predicate) {
284        assert predicate.getConstrainedColumn() == getConstrainedColumn();
285        if (predicate instanceof ListColumnPredicate) {
286            ListColumnPredicate that = (ListColumnPredicate) predicate;
287            final List<StarColumnPredicate> list =
288                new ArrayList<StarColumnPredicate>(children);
289            list.addAll(that.children);
290            return new ListColumnPredicate(
291                getConstrainedColumn(),
292                list);
293        } else {
294            final List<StarColumnPredicate> list =
295                new ArrayList<StarColumnPredicate>(children);
296            list.add(predicate);
297            return new ListColumnPredicate(
298                getConstrainedColumn(),
299                list);
300        }
301    }
302
303    public StarColumnPredicate cloneWithColumn(RolapStar.Column column) {
304        return new ListColumnPredicate(
305            column,
306            cloneListWithColumn(column, children));
307    }
308
309    public void toSql(SqlQuery sqlQuery, StringBuilder buf) {
310        List<StarColumnPredicate> predicates = getPredicates();
311        if (predicates.size() == 1) {
312            predicates.get(0).toSql(sqlQuery, buf);
313            return;
314        }
315
316        int notNullCount = 0;
317        final RolapStar.Column column = getConstrainedColumn();
318        final String expr = column.generateExprString(sqlQuery);
319        final int marker = buf.length(); // to allow backtrack later
320        buf.append(expr);
321        ValueColumnPredicate firstNotNull = null;
322        buf.append(" in (");
323        for (StarColumnPredicate predicate1 : predicates) {
324            final ValueColumnPredicate predicate2 =
325                (ValueColumnPredicate) predicate1;
326            Object key = predicate2.getValue();
327            if (key == RolapUtil.sqlNullValue) {
328                continue;
329            }
330            if (notNullCount > 0) {
331                buf.append(", ");
332            } else {
333                firstNotNull = predicate2;
334            }
335            ++notNullCount;
336            sqlQuery.getDialect().quote(buf, key, column.getDatatype());
337        }
338        buf.append(')');
339
340        // If all of the predicates were non-null, return what we've got, for
341        // example, "x in (1, 2, 3)".
342        if (notNullCount >= predicates.size()) {
343            return;
344        }
345
346        // There was at least one null. Reset the buffer to how we
347        // originally found it, and generate a more concise expression.
348        switch (notNullCount) {
349        case 0:
350            // Special case -- there were no values besides null.
351            // Return, for example, "x is null".
352            buf.setLength(marker);
353            buf.append(expr);
354            buf.append(" is null");
355            break;
356
357        case 1:
358            // Special case -- one not-null value, and null, for
359            // example "(x = 1 or x is null)".
360            assert firstNotNull != null;
361            buf.setLength(marker);
362            buf.append('(');
363            buf.append(expr);
364            buf.append(" = ");
365            sqlQuery.getDialect().quote(
366                buf,
367                firstNotNull.getValue(),
368                column.getDatatype());
369            buf.append(" or ");
370            buf.append(expr);
371            buf.append(" is null)");
372            break;
373
374        default:
375            // Nulls and values, for example,
376            // "(x in (1, 2) or x IS NULL)".
377            String save = buf.substring(marker);
378            buf.setLength(marker); // backtrack
379            buf.append('(');
380            buf.append(save);
381            buf.append(" or ");
382            buf.append(expr);
383            buf.append(" is null)");
384            break;
385        }
386    }
387}
388
389// End ListColumnPredicate.java