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