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