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