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; 011 012import mondrian.calc.*; 013import mondrian.calc.impl.*; 014import mondrian.olap.*; 015import mondrian.olap.type.SetType; 016 017import java.io.PrintWriter; 018import java.io.StringWriter; 019import java.util.*; 020 021/** 022 * Evaluator which checks dependencies of expressions. 023 * 024 * <p>For each expression evaluation, this valuator evaluates each 025 * expression more times, and makes sure that the results of the expression 026 * are independent of dimensions which the expression claims to be 027 * independent of. 028 * 029 * <p>Since it evaluates each expression twice, it also exposes function 030 * implementations which change the context of the evaluator. 031 * 032 * @author jhyde 033 * @since September, 2005 034 */ 035public class RolapDependencyTestingEvaluator extends RolapEvaluator { 036 037 /** 038 * Creates an dependency-testing evaluator. 039 * 040 * @param result Result we are building 041 * @param expDeps Number of dependencies to check 042 */ 043 RolapDependencyTestingEvaluator(RolapResult result, int expDeps) { 044 super(new DteRoot(result, expDeps)); 045 } 046 047 /** 048 * Creates a child evaluator. 049 * 050 * @param root Root evaluation context 051 * @param evaluator Parent evaluator 052 */ 053 private RolapDependencyTestingEvaluator( 054 RolapEvaluatorRoot root, 055 RolapDependencyTestingEvaluator evaluator, 056 List<List<Member>> aggregationList) 057 { 058 super(root, evaluator, aggregationList); 059 } 060 061 public Object evaluate( 062 Calc calc, 063 Hierarchy[] independentHierarchies, 064 String mdxString) 065 { 066 final DteRoot dteRoot = 067 (DteRoot) root; 068 if (dteRoot.faking) { 069 ++dteRoot.fakeCallCount; 070 } else { 071 ++dteRoot.callCount; 072 } 073 // Evaluate the call for real. 074 final Object result = calc.evaluate(this); 075 if (dteRoot.result.isDirty()) { 076 return result; 077 } 078 079 // If the result is a list and says that it is mutable, see whether it 080 // really is. 081 if (calc.getResultStyle() == ResultStyle.MUTABLE_LIST) { 082 List<Object> list = (List) result; 083 if (list.size() > 0) { 084 final Object zeroth = list.get(0); 085 list.set(0, zeroth); 086 } 087 } 088 089 // Change one of the allegedly independent dimensions and evaluate 090 // again. 091 // 092 // Don't do it if the faking is disabled, 093 // or if we're already faking another dimension, 094 // or if we're filtering out nonempty cells (which makes us 095 // dependent on everything), 096 // or if the ratio of fake evals to real evals is too high (which 097 // would make us too slow). 098 if (dteRoot.disabled 099 || dteRoot.faking 100 || isNonEmpty() 101 || (double) dteRoot.fakeCallCount 102 > (double) dteRoot.callCount * dteRoot.random.nextDouble() 103 * 2 * dteRoot.expDeps) 104 { 105 return result; 106 } 107 if (independentHierarchies.length == 0) { 108 return result; 109 } 110 dteRoot.faking = true; 111 ++dteRoot.fakeCount; 112 ++dteRoot.fakeCallCount; 113 final int i = dteRoot.random.nextInt(independentHierarchies.length); 114 final Member saveMember = getContext(independentHierarchies[i]); 115 final Member otherMember = 116 dteRoot.chooseOtherMember( 117 saveMember, getQuery().getSchemaReader(false)); 118 setContext(otherMember); 119 final Object otherResult = calc.evaluate(this); 120 if (false) { 121 System.out.println( 122 "original=" + saveMember.getUniqueName() 123 + ", member=" + otherMember.getUniqueName() 124 + ", originalResult=" + result 125 + ", result=" + otherResult); 126 } 127 if (!equals(otherResult, result)) { 128 final Member[] members = getMembers(); 129 final StringBuilder buf = new StringBuilder(); 130 for (int j = 0; j < members.length; j++) { 131 if (j > 0) { 132 buf.append(", "); 133 } 134 buf.append(members[j].getUniqueName()); 135 } 136 throw Util.newInternal( 137 "Expression '" + mdxString 138 + "' claims to be independent of dimension " 139 + saveMember.getDimension() + " but is not; context is {" 140 + buf.toString() + "}; First result: " 141 + toString(result) + ", Second result: " 142 + toString(otherResult)); 143 } 144 // Restore context. 145 setContext(saveMember); 146 dteRoot.faking = false; 147 return result; 148 } 149 150 public RolapEvaluator _push(List<List<Member>> aggregationList) { 151 return new RolapDependencyTestingEvaluator(root, this, aggregationList); 152 } 153 154 private boolean equals(Object o1, Object o2) { 155 if (o1 == null) { 156 return o2 == null; 157 } 158 if (o2 == null) { 159 return false; 160 } 161 if (o1 instanceof Object[]) { 162 if (o2 instanceof Object[]) { 163 Object[] a1 = (Object[]) o1; 164 Object[] a2 = (Object[]) o2; 165 if (a1.length == a2.length) { 166 for (int i = 0; i < a1.length; i++) { 167 if (!equals(a1[i], a2[i])) { 168 return false; 169 } 170 } 171 return true; 172 } 173 } 174 return false; 175 } 176 if (o1 instanceof List) { 177 return o2 instanceof List 178 && equals( 179 ((List) o1).toArray(), 180 ((List) o2).toArray()); 181 } 182 if (o1 instanceof Iterable) { 183 if (o2 instanceof Iterable) { 184 return equals(toList((Iterable) o1), toList((Iterable) o2)); 185 } else { 186 return false; 187 } 188 } 189 return o1.equals(o2); 190 } 191 192 private String toString(Object o) { 193 StringWriter sw = new StringWriter(); 194 PrintWriter pw = new PrintWriter(sw); 195 toString(o, pw); 196 return sw.toString(); 197 } 198 199 private <T> List<T> toList(Iterable<T> iterable) { 200 final ArrayList<T> list = new ArrayList<T>(); 201 for (T t : iterable) { 202 list.add(t); 203 } 204 return list; 205 } 206 207 private void toString(Object o, PrintWriter pw) { 208 if (o instanceof Object[]) { 209 Object[] a = (Object[]) o; 210 pw.print("{"); 211 for (int i = 0; i < a.length; i++) { 212 Object o1 = a[i]; 213 if (i > 0) { 214 pw.print(", "); 215 } 216 toString(o1, pw); 217 } 218 pw.print("}"); 219 } else if (o instanceof List) { 220 List list = (List) o; 221 toString(list.toArray(), pw); 222 } else if (o instanceof Member) { 223 Member member = (Member) o; 224 pw.print(member.getUniqueName()); 225 } else { 226 pw.print(o); 227 } 228 } 229 230 /** 231 * Holds context for a tree of {@link RolapDependencyTestingEvaluator}. 232 */ 233 static class DteRoot extends RolapResult.RolapResultEvaluatorRoot { 234 final int expDeps; 235 int callCount; 236 int fakeCallCount; 237 int fakeCount; 238 boolean faking; 239 boolean disabled; 240 final Random random = Util.createRandom( 241 MondrianProperties.instance().TestSeed.get()); 242 243 DteRoot(RolapResult result, int expDeps) { 244 super(result); 245 this.expDeps = expDeps; 246 } 247 248 /** 249 * Chooses another member of the same hierarchy. 250 * The member will come from all levels with the same probability. 251 * Calculated members are not included. 252 * 253 * @param save Previous member 254 * @param schemaReader Schema reader 255 * @return other member of same hierarchy 256 */ 257 private Member chooseOtherMember( 258 final Member save, 259 SchemaReader schemaReader) 260 { 261 final Hierarchy hierarchy = save.getHierarchy(); 262 int attempt = 0; 263 while (true) { 264 // Choose a random level. 265 final Level[] levels = hierarchy.getLevels(); 266 final int levelDepth = random.nextInt(levels.length) + 1; 267 Member member = null; 268 for (int i = 0; i < levelDepth; i++) { 269 List<Member> members; 270 if (i == 0) { 271 members = 272 schemaReader.getLevelMembers(levels[i], false); 273 } else { 274 members = schemaReader.getMemberChildren(member); 275 } 276 if (members.size() == 0) { 277 break; 278 } 279 member = members.get(random.nextInt(members.size())); 280 } 281 // If the member chosen happens to be the same as the original 282 // member, try again. Give up after 100 attempts (in case the 283 // hierarchy has only one member). 284 if (member != save || ++attempt > 100) { 285 return member; 286 } 287 } 288 } 289 } 290 291 /** 292 * Expression which checks dependencies of an underlying scalar expression. 293 */ 294 private static class DteScalarCalcImpl extends GenericCalc { 295 private final Calc calc; 296 private final Hierarchy[] independentHierarchies; 297 private final String mdxString; 298 299 DteScalarCalcImpl( 300 Calc calc, 301 Hierarchy[] independentHierarchies, 302 String mdxString) 303 { 304 super(new DummyExp(calc.getType())); 305 this.calc = calc; 306 this.independentHierarchies = independentHierarchies; 307 this.mdxString = mdxString; 308 } 309 310 public Calc[] getCalcs() { 311 return new Calc[] {calc}; 312 } 313 314 public Object evaluate(Evaluator evaluator) { 315 RolapDependencyTestingEvaluator dtEval = 316 (RolapDependencyTestingEvaluator) evaluator; 317 return dtEval.evaluate(calc, independentHierarchies, mdxString); 318 } 319 320 public ResultStyle getResultStyle() { 321 return calc.getResultStyle(); 322 } 323 } 324 325 /** 326 * Expression which checks dependencies and list immutability of an 327 * underlying list or iterable expression. 328 */ 329 private static class DteIterCalcImpl extends GenericIterCalc { 330 private final Calc calc; 331 private final Hierarchy[] independentHierarchies; 332 private final boolean mutableList; 333 private final String mdxString; 334 335 DteIterCalcImpl( 336 Calc calc, 337 Hierarchy[] independentHierarchies, 338 boolean mutableList, 339 String mdxString) 340 { 341 super(new DummyExp(calc.getType())); 342 this.calc = calc; 343 this.independentHierarchies = independentHierarchies; 344 this.mutableList = mutableList; 345 this.mdxString = mdxString; 346 } 347 348 public Calc[] getCalcs() { 349 return new Calc[] {calc}; 350 } 351 352 public Object evaluate(Evaluator evaluator) { 353 RolapDependencyTestingEvaluator dtEval = 354 (RolapDependencyTestingEvaluator) evaluator; 355 return dtEval.evaluate(calc, independentHierarchies, mdxString); 356 } 357 358 public TupleList evaluateList(Evaluator evaluator) { 359 TupleList list = super.evaluateList(evaluator); 360 if (!mutableList) { 361 list = TupleCollections.unmodifiableList(list); 362 } 363 return list; 364 } 365 366 public ResultStyle getResultStyle() { 367 return calc.getResultStyle(); 368 } 369 } 370 371 /** 372 * Expression compiler which introduces dependency testing. 373 * 374 * <p>It also checks that the caller does not modify lists unless it has 375 * explicitly asked for a mutable list. 376 */ 377 static class DteCompiler extends DelegatingExpCompiler { 378 DteCompiler(ExpCompiler compiler) { 379 super(compiler); 380 } 381 382 protected Calc afterCompile(Exp exp, Calc calc, boolean mutable) { 383 Hierarchy[] dimensions = getIndependentHierarchies(calc); 384 calc = super.afterCompile(exp, calc, mutable); 385 if (calc.getType() instanceof SetType) { 386 return new DteIterCalcImpl( 387 calc, 388 dimensions, 389 mutable, 390 Util.unparse(exp)); 391 } else { 392 return new DteScalarCalcImpl( 393 calc, 394 dimensions, 395 Util.unparse(exp)); 396 } 397 } 398 399 /** 400 * Returns the dimensions an expression does not depend on. If the 401 * current member of any of these dimensions changes, the expression 402 * will return the same result. 403 * 404 * @param calc Expression 405 * @return List of dimensions that the expression does not depend on 406 */ 407 private Hierarchy[] getIndependentHierarchies(Calc calc) { 408 List<Hierarchy> list = new ArrayList<Hierarchy>(); 409 final List<RolapHierarchy> hierarchies = 410 ((RolapCube) getValidator().getQuery().getCube()) 411 .getHierarchies(); 412 for (Hierarchy hierarchy : hierarchies) { 413 if (!calc.dependsOn(hierarchy)) { 414 list.add(hierarchy); 415 } 416 } 417 return list.toArray(new Hierarchy[list.size()]); 418 } 419 } 420} 421 422// End RolapDependencyTestingEvaluator.java