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