001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/agg/OrPredicate.java#5 $
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) 2007-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.rolap.StarPredicate;
013    import mondrian.rolap.RolapStar;
014    import mondrian.rolap.BitKey;
015    import mondrian.rolap.sql.SqlQuery;
016    
017    import java.util.*;
018    
019    /**
020     * Predicate which is the union of a list of predicates. It evaluates to
021     * true if any of the predicates evaluates to true.
022     *
023     * @see OrPredicate
024     *
025     * @author jhyde
026     * @version $Id: //open/mondrian/src/main/mondrian/rolap/agg/OrPredicate.java#5 $
027     */
028    public class OrPredicate extends ListPredicate {
029    
030        public OrPredicate(List<StarPredicate> predicateList) {
031            super(predicateList);
032        }
033    
034        public boolean evaluate(List<Object> valueList) {
035            // NOTE: If we know that every predicate in the list is a
036            // ValueColumnPredicate, we could optimize the evaluate method by
037            // building a value list at construction time. But it's a tradeoff,
038            // considering the extra time and space required.
039            for (StarPredicate childPredicate : children) {
040                if (childPredicate.evaluate(valueList)) {
041                    return true;
042                }
043            }
044            return false;
045        }
046    
047        public StarPredicate or(StarPredicate predicate) {
048            if (predicate instanceof OrPredicate &&
049                predicate.getConstrainedColumnBitKey().equals(getConstrainedColumnBitKey())) {
050                // Do not collapse OrPredicates with different number of columns.
051                // Keeping them separate helps the SQL translation to IN-list.
052                ListPredicate that = (ListPredicate) predicate;
053                final List<StarPredicate> list =
054                    new ArrayList<StarPredicate>(children);
055                list.addAll(that.children);
056                return new OrPredicate(list);
057            } else {
058                final List<StarPredicate> list =
059                    new ArrayList<StarPredicate>(children);
060                list.add(predicate);
061                return new OrPredicate(list);
062            }
063        }
064    
065        public StarPredicate and(StarPredicate predicate) {
066            List<StarPredicate> list = new ArrayList<StarPredicate>();
067            list.add(this);
068            list.add(predicate);
069            return new AndPredicate(list);
070        }
071    
072        /**
073         * Checks whether a predicate can be translated using an IN list, and groups
074         * predicates based on how many columns can be translated using IN list. If
075         * none of the columns can be made part of IN, the entire predicate will be
076         * translated using AND/OR. This method identifies all the columns that can
077         * be part of IN and and categorizes this predicate based on number of
078         * column values to use in the IN list.
079         *
080         * @param predicate predicate to analyze
081         * @param sqlQuery Query
082         * @param predicateMap the map containing predicates analyzed so far
083         */
084        private void checkInListForPredicate(
085            StarPredicate predicate,
086            SqlQuery sqlQuery,
087            Map<BitKey, List<StarPredicate>> predicateMap) {
088            BitKey inListRHSBitKey;
089    
090            if (predicate instanceof ValueColumnPredicate) {
091                // OR of column values from the same column
092                inListRHSBitKey =
093                    ((ValueColumnPredicate) predicate).checkInList(columnBitKey);
094            } else if (predicate instanceof AndPredicate) {
095                // OR of ANDs over a set of values over the same column set
096                inListRHSBitKey =
097                      ((AndPredicate) predicate).checkInList(sqlQuery, columnBitKey);
098            } else {
099                inListRHSBitKey = columnBitKey.emptyCopy();
100            }
101            List<StarPredicate> predicateGroup =
102                predicateMap.get(inListRHSBitKey);
103            if (predicateGroup == null) {
104                predicateGroup = new ArrayList<StarPredicate> ();
105                predicateMap.put(inListRHSBitKey, predicateGroup);
106            }
107            predicateGroup.add(predicate);
108        }
109    
110        private void checkInList(
111            SqlQuery sqlQuery,
112            Map<BitKey, List<StarPredicate>> predicateMap)
113        {
114            for (StarPredicate predicate : children) {
115                checkInListForPredicate(predicate, sqlQuery, predicateMap);
116            }
117        }
118    
119        /**
120         * Translates a list of predicates over the same set of columns into sql
121         * using IN list where possible.
122         *
123         * @param sqlQuery Query
124         * @param buf buffer to build sql
125         * @param inListRHSBitKey which column positions are included in the IN predicate
126         * The non included positions corresponde to columns that are nulls.
127         * @param predicateList the list of predicates to translate.
128         */
129        private void toInListSql(
130            SqlQuery sqlQuery,
131            StringBuilder buf,
132            BitKey inListRHSBitKey,
133            List<StarPredicate> predicateList)
134        {
135            // Make a col position to column map to aid search.
136            Map<Integer, RolapStar.Column> columnMap =
137                new HashMap<Integer, RolapStar.Column>();
138    
139            for (RolapStar.Column column : columns) {
140                columnMap.put(column.getBitPosition(), column);
141            }
142    
143            buf.append("(");
144            // First generate nulls for the columns which will not be included
145            // in the IN list
146    
147            boolean firstNullColumnPredicate = true;
148            for (Integer colPos : columnBitKey.andNot(inListRHSBitKey)) {
149                if (firstNullColumnPredicate) {
150                    firstNullColumnPredicate = false;
151                } else {
152                    buf.append(" and ");
153                }
154                String expr = columnMap.get(colPos).generateExprString(sqlQuery);
155                buf.append(expr);
156                buf.append(" is null");
157            }
158    
159            // Now the IN list part
160            if (inListRHSBitKey.isEmpty()) {
161                return;
162            }
163    
164            if (firstNullColumnPredicate) {
165                firstNullColumnPredicate = false;
166            } else {
167                buf.append(" and ");
168            }
169    
170            // First add the column names;
171            boolean multiInList = inListRHSBitKey.toBitSet().cardinality() > 1;
172            if (multiInList) {
173                // Multi-IN list
174                buf.append("(");
175            }
176    
177            boolean firstColumn = true;
178            for (Integer colPos : inListRHSBitKey) {
179                if (firstColumn) {
180                    firstColumn = false;
181                } else {
182                    buf.append(", ");
183                }
184                String expr = columnMap.get(colPos).generateExprString(sqlQuery);
185                buf.append(expr);
186            }
187            if (multiInList) {
188                // Multi-IN list
189                buf.append(")");
190            }
191            buf.append(" in (");
192    
193            boolean firstPredicate = true;
194            for (StarPredicate predicate : predicateList) {
195                if (firstPredicate) {
196                    firstPredicate = false;
197                } else {
198                    buf.append(", ");
199                }
200    
201                if (predicate instanceof AndPredicate) {
202                    ((AndPredicate)predicate).toInListSql(sqlQuery, buf, inListRHSBitKey);
203                } else {
204                    assert (predicate instanceof ValueColumnPredicate);
205                    ((ValueColumnPredicate)predicate).toInListSql(sqlQuery, buf);
206                }
207            }
208            buf.append(")");
209            buf.append(")");
210        }
211    
212        public void toSql(SqlQuery sqlQuery, StringBuilder buf) {
213            //
214            // If possible, translate the predicate using IN lists.
215            //
216            // Two possibilities:
217            // (1) top-level can be directly tranlated to IN-list
218            //  examples:
219            //   (country IN (USA, Canada))
220            //
221            //   ((country, satte) in ((USA, CA), (USA, OR)))
222            //
223            // (2) child level can be translated to IN list: this allows IN list
224            // predicates generated such as:
225            //   (country, state) IN ((USA, CA), (USA, OR))
226            //   OR
227            //   (country, state, city) IN ((USA, CA, SF), (USA, OR, Portland))
228            //
229            // The second case is handled by calling toSql on the children in
230            // super.toSql().
231            //
232            Map<BitKey, List<StarPredicate>> predicateMap =
233                new HashMap<BitKey, List<StarPredicate>> ();
234    
235            boolean first = true;
236            checkInList(sqlQuery, predicateMap);
237            buf.append("(");
238    
239            for (BitKey columnKey : predicateMap.keySet()) {
240                List<StarPredicate> predList = predicateMap.get(columnKey);
241                if (columnKey.isEmpty() || predList.size() <= 1) {
242                    // Not possible to have IN list, or only one predicate
243                    // in the group.
244                    for (StarPredicate pred : predList) {
245                        if (first) {
246                            first = false;
247                        } else {
248                            buf.append(" or ");
249                        }
250                        pred.toSql(sqlQuery, buf);
251                    }
252                } else {
253                    // Translate the rest
254                    if (first) {
255                        first = false;
256                    } else {
257                        buf.append(" or ");
258                    }
259                    toInListSql(sqlQuery, buf, columnKey, predList);
260                }
261            }
262    
263            buf.append(")");
264        }
265    
266        protected String getOp() {
267            return "or";
268        }
269    }
270    
271    // End OrPredicate.java