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