001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapEvaluator.java#81 $ 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) 2001-2002 Kana Software, Inc. 007 // Copyright (C) 2001-2008 Julian Hyde and others 008 // All Rights Reserved. 009 // You must accept the terms of that agreement to use this software. 010 // 011 // jhyde, 10 August, 2001 012 */ 013 014 package mondrian.rolap; 015 import mondrian.calc.*; 016 import mondrian.mdx.ResolvedFunCall; 017 import mondrian.olap.*; 018 import mondrian.olap.fun.FunUtil; 019 import mondrian.olap.fun.AggregateFunDef; 020 import mondrian.rolap.sql.SqlQuery; 021 import mondrian.resource.MondrianResource; 022 import mondrian.util.Format; 023 024 import org.apache.log4j.Logger; 025 026 import java.util.*; 027 028 /** 029 * <code>RolapEvaluator</code> evaluates expressions in a dimensional 030 * environment. 031 * 032 * <p>The context contains a member (which may be the default member) 033 * for every dimension in the current cube. Certain operations, such as 034 * evaluating a calculated member or a tuple, change the current context. The 035 * evaluator's {@link #push} method creates a clone of the current evaluator 036 * so that you can revert to the original context once the operation has 037 * completed. 038 * 039 * <h3>Developers note</h3> 040 * 041 * <p>Many of the methods in this class are performance-critical. Where 042 * possible they are declared 'final' so that the JVM can optimize calls to 043 * these methods. If future functionality requires it, the 'final' modifier 044 * can be removed and these methods can be overridden. 045 * 046 * @author jhyde 047 * @since 10 August, 2001 048 * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapEvaluator.java#81 $ 049 */ 050 public class RolapEvaluator implements Evaluator { 051 private static final Logger LOGGER = Logger.getLogger(RolapEvaluator.class); 052 053 /** 054 * Dummy value to represent null results in the expression cache. 055 */ 056 private static final Object nullResult = new Object(); 057 058 private final RolapMember[] currentMembers; 059 private final Evaluator parent; 060 protected CellReader cellReader; 061 private final int depth; 062 063 private Member expandingMember; 064 private boolean nonEmpty; 065 protected final RolapEvaluatorRoot root; 066 private int iterationLength; 067 private boolean evalAxes; 068 069 private final Member[] calcMembers; 070 private int calcMemberCount; 071 072 /** 073 * List of lists of tuples or members, rarely used, but overrides the 074 * ordinary dimensional context if set when a cell value comes to be 075 * evaluated. 076 */ 077 protected List<List<Member[]>> aggregationLists; 078 079 private final List<Member> slicerMembers; 080 081 private final MondrianProperties.SolveOrderModeEnum solveOrderMode = 082 Util.lookup( 083 MondrianProperties.SolveOrderModeEnum.class, 084 MondrianProperties.instance().SolveOrderMode.get().toUpperCase(), 085 MondrianProperties.SolveOrderModeEnum.ABSOLUTE); 086 087 /** 088 * States of the finite state machine for determining the max solve order 089 * for the "scoped" behavior. 090 */ 091 private enum ScopedMaxSolveOrderFinderState { 092 START, 093 AGG_SCOPE, 094 CUBE_SCOPE, 095 QUERY_SCOPE 096 }; 097 098 /** 099 * Creates an evaluator. 100 * 101 * @param root Root context for stack of evaluators (contains information 102 * which does not change during the evaluation) 103 * @param parent Parent evaluator, or null if this is the root 104 */ 105 protected RolapEvaluator( 106 RolapEvaluatorRoot root, 107 RolapEvaluator parent) { 108 this.iterationLength = 1; 109 this.root = root; 110 this.parent = parent; 111 112 if (parent == null) { 113 depth = 0; 114 nonEmpty = false; 115 evalAxes = false; 116 cellReader = null; 117 currentMembers = new RolapMember[root.cube.getDimensions().length]; 118 calcMembers = new Member[this.currentMembers.length]; 119 calcMemberCount = 0; 120 slicerMembers = new ArrayList<Member>(); 121 aggregationLists = null; 122 } else { 123 depth = parent.depth + 1; 124 nonEmpty = parent.nonEmpty; 125 evalAxes = parent.evalAxes; 126 cellReader = parent.cellReader; 127 currentMembers = parent.currentMembers.clone(); 128 calcMembers = parent.calcMembers.clone(); 129 calcMemberCount = parent.calcMemberCount; 130 slicerMembers = new ArrayList<Member> (parent.slicerMembers); 131 if (parent.aggregationLists != null) { 132 aggregationLists = 133 new ArrayList<List<Member[]>>(parent.aggregationLists); 134 } else { 135 aggregationLists = null; 136 } 137 } 138 } 139 140 /** 141 * Creates an evaluator with no parent. 142 * 143 * @param root Shared context between this evaluator and its children 144 */ 145 public RolapEvaluator(RolapEvaluatorRoot root) { 146 this(root, null); 147 148 // we expect client to set CellReader 149 150 final SchemaReader scr = this.root.schemaReader; 151 final Dimension[] dimensions = this.root.cube.getDimensions(); 152 for (final Dimension dimension : dimensions) { 153 final int ordinal = dimension.getOrdinal(this.root.cube); 154 final Hierarchy hier = dimension.getHierarchy(); 155 156 final RolapMember member = 157 (RolapMember) scr.getHierarchyDefaultMember(hier); 158 159 // If there is no member, we cannot continue. 160 if (member == null) { 161 throw MondrianResource.instance().InvalidHierarchyCondition 162 .ex(hier.getUniqueName()); 163 } 164 165 // This fragment is a concurrency bottleneck, so use a cache of 166 // hierarchy usages. 167 final HierarchyUsage hierarchyUsage = 168 this.root.cube.getFirstUsage(hier); 169 if (hierarchyUsage != null) { 170 member.makeUniqueName(hierarchyUsage); 171 } 172 173 currentMembers[ordinal] = member; 174 if (member.isCalculated()) { 175 addCalcMember(member); 176 } 177 } 178 179 root.init(this); 180 } 181 182 /** 183 * Creates an evaluator. 184 */ 185 public static Evaluator create(Query query) { 186 final RolapEvaluatorRoot root = new RolapEvaluatorRoot(query); 187 return new RolapEvaluator(root); 188 } 189 190 /** 191 * Returns the base (non-virtual) cube that the current measure in the 192 * context belongs to. 193 * @return Cube 194 */ 195 public RolapCube getMeasureCube() { 196 RolapCube measureCube = null; 197 if (currentMembers[0] instanceof RolapStoredMeasure) { 198 measureCube = ((RolapStoredMeasure) currentMembers[0]).getCube(); 199 } 200 return measureCube; 201 } 202 203 /** 204 * If IgnoreMeasureForNonJoiningDimension is set to true and one or more 205 * members are on unrelated dimension for the measure in current context 206 * then returns true. 207 * @param members 208 * dimensions for the members need to be checked whether 209 * related or unrelated 210 * @return boolean 211 */ 212 public boolean needToReturnNullForUnrelatedDimension(Member[] members) { 213 RolapCube virtualCube = getCube(); 214 RolapCube baseCube = getMeasureCube(); 215 if (virtualCube.isVirtual() && baseCube != null) { 216 if (virtualCube.shouldIgnoreUnrelatedDimensions(baseCube.getName())) { 217 return false; 218 } else if (MondrianProperties.instance() 219 .IgnoreMeasureForNonJoiningDimension.get()) { 220 Set<Dimension> nonJoiningDimensions = 221 baseCube.nonJoiningDimensions(members); 222 if (!nonJoiningDimensions.isEmpty()) { 223 return true; 224 } 225 } 226 } 227 228 return false; 229 } 230 231 protected static class RolapEvaluatorRoot { 232 final Map<Object, Object> expResultCache = 233 new HashMap<Object, Object>(); 234 final Map<Object, Object> tmpExpResultCache = 235 new HashMap<Object, Object>(); 236 final RolapCube cube; 237 final RolapConnection connection; 238 final SchemaReader schemaReader; 239 final Map<List<Object>, Calc> compiledExps = 240 new HashMap<List<Object>, Calc>(); 241 private final Query query; 242 private final Date queryStartTime; 243 final SqlQuery.Dialect currentDialect; 244 245 /** 246 * Default members of each hierarchy, from the schema reader's 247 * perspective. Finding the default member is moderately expensive, but 248 * happens very often. 249 */ 250 private final RolapMember[] defaultMembers; 251 252 public RolapEvaluatorRoot(Query query) { 253 this.query = query; 254 this.cube = (RolapCube) query.getCube(); 255 this.connection = (RolapConnection) query.getConnection(); 256 this.schemaReader = query.getSchemaReader(true); 257 this.queryStartTime = new Date(); 258 List<RolapMember> list = new ArrayList<RolapMember>(); 259 for (Dimension dimension : cube.getDimensions()) { 260 list.add( 261 (RolapMember) schemaReader.getHierarchyDefaultMember( 262 dimension.getHierarchy())); 263 } 264 this.defaultMembers = list.toArray(new RolapMember[list.size()]); 265 this.currentDialect = SqlQuery.Dialect.create(schemaReader.getDataSource()); 266 } 267 268 /** 269 * Implements a cheap-and-cheerful mapping from expressions to compiled 270 * expressions. 271 * 272 * <p>TODO: Save compiled expressions somewhere better. 273 * 274 * @param exp Expression 275 * @param scalar Whether expression is scalar 276 * @param resultStyle Preferred result style; if null, use query's default 277 * result style; ignored if expression is scalar 278 * @return compiled expression 279 */ 280 final Calc getCompiled( 281 Exp exp, 282 boolean scalar, 283 ResultStyle resultStyle) 284 { 285 List<Object> key = Arrays.asList(exp, scalar, resultStyle); 286 Calc calc = compiledExps.get(key); 287 if (calc == null) { 288 calc = query.compileExpression(exp, scalar, resultStyle); 289 compiledExps.put(key, calc); 290 } 291 return calc; 292 } 293 294 /** 295 * Evaluates a named set. 296 * 297 * <p>The default implementation throws 298 * {@link UnsupportedOperationException}. 299 */ 300 protected Object evaluateNamedSet(String name, Exp exp) { 301 throw new UnsupportedOperationException(); 302 } 303 304 /** 305 * First evaluator calls this method on construction. 306 */ 307 protected void init(Evaluator evaluator) { 308 } 309 310 /** 311 * Returns the value of a parameter, evaluating its default expression 312 * if necessary. 313 * 314 * <p>The default implementation throws 315 * {@link UnsupportedOperationException}. 316 */ 317 public Object getParameterValue(ParameterSlot slot) { 318 throw new UnsupportedOperationException(); 319 } 320 321 /** 322 * Puts result in cache. 323 * 324 * @param key key 325 * @param result value to be cached 326 * @param isValidResult indicate if this result is valid 327 */ 328 public final void putCacheResult( 329 Object key, 330 Object result, 331 boolean isValidResult) 332 { 333 if (isValidResult) { 334 expResultCache.put(key, result); 335 } else { 336 tmpExpResultCache.put(key, result); 337 } 338 } 339 340 /** 341 * Gets result from cache. 342 * 343 * @param key cache key 344 * @return cached expression 345 */ 346 public final Object getCacheResult(Object key) { 347 Object result = expResultCache.get(key); 348 if (result == null) { 349 result = tmpExpResultCache.get(key); 350 } 351 return result; 352 } 353 354 /** 355 * Clears the expression result cache. 356 * 357 * @param clearValidResult whether to clear valid expression results 358 */ 359 public final void clearResultCache(boolean clearValidResult) { 360 if (clearValidResult) { 361 expResultCache.clear(); 362 } 363 tmpExpResultCache.clear(); 364 } 365 366 /** 367 * Get query start time. 368 * 369 * @return the query start time 370 */ 371 public Date getQueryStartTime() { 372 return queryStartTime; 373 } 374 } 375 376 protected final Logger getLogger() { 377 return LOGGER; 378 } 379 380 public final Member[] getMembers() { 381 return currentMembers; 382 } 383 384 public final List<List<Member[]>> getAggregationLists() { 385 return aggregationLists; 386 } 387 388 final void setCellReader(CellReader cellReader) { 389 this.cellReader = cellReader; 390 } 391 392 public final RolapCube getCube() { 393 return root.cube; 394 } 395 396 public final Query getQuery() { 397 return root.query; 398 } 399 400 public final int getDepth() { 401 return depth; 402 } 403 404 public final Evaluator getParent() { 405 return parent; 406 } 407 408 public final SchemaReader getSchemaReader() { 409 return root.schemaReader; 410 } 411 412 public Date getQueryStartTime() { 413 return root.getQueryStartTime(); 414 } 415 416 public SqlQuery.Dialect getDialect() { 417 return root.currentDialect; 418 } 419 420 public final RolapEvaluator push(Member[] members) { 421 final RolapEvaluator evaluator = _push(); 422 evaluator.setContext(members); 423 return evaluator; 424 } 425 426 public final RolapEvaluator push(Member member) { 427 final RolapEvaluator evaluator = _push(); 428 evaluator.setContext(member); 429 return evaluator; 430 } 431 432 public final RolapEvaluator push() { 433 return _push(); 434 } 435 436 /** 437 * Creates a clone of the current validator. 438 */ 439 protected RolapEvaluator _push() { 440 getQuery().checkCancelOrTimeout(); 441 return new RolapEvaluator(root, this); 442 } 443 444 public final Evaluator pop() { 445 return parent; 446 } 447 448 public final Evaluator pushAggregation(List<Member[]> list) { 449 RolapEvaluator newEvaluator = _push(); 450 newEvaluator.addToAggregationList(list); 451 clearHierarchyFromRegularContext(list, newEvaluator); 452 return newEvaluator; 453 } 454 455 private void addToAggregationList(List<Member[]> list) { 456 if (aggregationLists == null) { 457 aggregationLists = new ArrayList<List<Member[]>>(); 458 } 459 aggregationLists.add(list); 460 } 461 462 private void clearHierarchyFromRegularContext( 463 List<Member[]> list, 464 RolapEvaluator newEvaluator) 465 { 466 Member[] tuple = list.get(0); 467 for (Member member : tuple) { 468 newEvaluator.setContext(member.getHierarchy().getAllMember()); 469 } 470 } 471 472 /** 473 * Returns true if the other object is a {@link RolapEvaluator} with 474 * identical context. 475 */ 476 public final boolean equals(Object obj) { 477 if (!(obj instanceof RolapEvaluator)) { 478 return false; 479 } 480 RolapEvaluator that = (RolapEvaluator) obj; 481 return Arrays.equals(this.currentMembers, that.currentMembers); 482 } 483 484 public final int hashCode() { 485 return Util.hashArray(0, this.currentMembers); 486 } 487 488 /** 489 * Adds a slicer member to the evaluator context, and remember it as part 490 * of the slicer. The slicer members are passed onto derived evaluators 491 * so that functions using those evaluators can choose to ignore the 492 * slicer members. One such function is CrossJoin emptiness check. 493 * 494 * @param member a member in the slicer 495 */ 496 public final void setSlicerContext(Member member) { 497 setContext(member); 498 slicerMembers.add(member); 499 } 500 501 /** 502 * Return the list of slicer members in the current evaluator context. 503 * @return slicerMembers 504 */ 505 public final List<Member> getSlicerMembers() { 506 return slicerMembers; 507 } 508 509 public final Member setContext(Member member) { 510 final RolapMember m = (RolapMember) member; 511 final int ordinal = m.getDimension().getOrdinal(root.cube); 512 final Member previous = currentMembers[ordinal]; 513 if (m.equals(previous)) { 514 return m; 515 } 516 if (previous.isCalculated()) { 517 removeCalcMember(previous); 518 } 519 currentMembers[ordinal] = m; 520 if (m.isCalculated()) { 521 addCalcMember(m); 522 } 523 return previous; 524 } 525 526 public final void setContext(List<Member> memberList) { 527 int i = 0; 528 for (Member member : memberList) { 529 // more than one usage 530 if (member == null) { 531 if (getLogger().isDebugEnabled()) { 532 getLogger().debug( 533 "RolapEvaluator.setContext: member == null " 534 + " , count=" + i); 535 } 536 assert false; 537 continue; 538 } 539 setContext(member); 540 } 541 } 542 543 public final void setContext(Member[] members) { 544 for (final Member member : members) { 545 // more than one usage 546 if (member == null) { 547 if (getLogger().isDebugEnabled()) { 548 getLogger().debug( 549 "RolapEvaluator.setContext: " 550 + "member == null, memberList: " 551 + Arrays.asList(members)); 552 } 553 assert false; 554 continue; 555 } 556 557 setContext(member); 558 } 559 } 560 561 public final RolapMember getContext(Dimension dimension) { 562 return currentMembers[dimension.getOrdinal(root.cube)]; 563 } 564 565 public final Object evaluateCurrent() { 566 // Get the member in the current context which is (a) calculated, and 567 // (b) has the highest solve order; returns null if there are no 568 // calculated members. 569 final Member maxSolveMember = peekCalcMember(); 570 if (maxSolveMember == null) { 571 final Object o = cellReader.get(this); 572 if (o == Util.nullValue) { 573 return null; 574 } 575 return o; 576 } 577 // REVIEW this operation is executed frequently, and computing the 578 // default member of a hierarchy for a given role is not cheap 579 final RolapMember defaultMember = 580 root.defaultMembers[ 581 maxSolveMember.getDimension().getOrdinal(root.cube)]; 582 583 final RolapEvaluator evaluator = push(defaultMember); 584 evaluator.setExpanding(maxSolveMember); 585 final Exp exp = maxSolveMember.getExpression(); 586 final Calc calc = root.getCompiled(exp, true, null); 587 final Object o = calc.evaluate(evaluator); 588 if (o == Util.nullValue) { 589 return null; 590 } 591 return o; 592 } 593 594 private void setExpanding(Member member) { 595 expandingMember = member; 596 final int memberCount = currentMembers.length; 597 if (depth > memberCount) { 598 if (depth % memberCount == 0) { 599 checkRecursion((RolapEvaluator) parent); 600 } 601 } 602 } 603 604 /** 605 * Returns the calculated member being currently expanded. 606 * 607 * <p>This can be useful if many calculated members are generated with 608 * essentially the same expression. The compiled expression can call this 609 * method to find which instance of the member is current, and therefore the 610 * calculated members can share the same {@link Calc} object. 611 * 612 * @return Calculated member currently being expanded 613 */ 614 Member getExpanding() { 615 return expandingMember; 616 } 617 618 /** 619 * Makes sure that there is no evaluator with identical context on the 620 * stack. 621 * 622 * @param eval Evaluator 623 * @throws mondrian.olap.fun.MondrianEvaluationException if there is a loop 624 */ 625 private static void checkRecursion(RolapEvaluator eval) { 626 // Find the nearest ancestor which is expanding a calculated member. 627 // (The starting evaluator has just been pushed, so may not have the 628 // state it will have when recursion happens.) 629 while (true) { 630 if (eval == null) { 631 return; 632 } 633 if (eval.expandingMember != null) { 634 break; 635 } 636 eval = (RolapEvaluator) eval.getParent(); 637 } 638 639 outer: 640 for (RolapEvaluator eval2 = (RolapEvaluator) eval.getParent(); 641 eval2 != null; 642 eval2 = (RolapEvaluator) eval2.getParent()) { 643 if (eval2.expandingMember != eval.expandingMember) { 644 continue; 645 } 646 for (int i = 0; i < eval.currentMembers.length; i++) { 647 final Member member = eval2.currentMembers[i]; 648 649 // more than one usage 650 if (member == null) { 651 if (LOGGER.isDebugEnabled()) { 652 LOGGER.debug( 653 "RolapEvaluator.checkRecursion: member == null " 654 + " , count=" + i); 655 } 656 continue; 657 } 658 659 final RolapMember parentMember = 660 eval.getContext(member.getDimension()); 661 if (member != parentMember) { 662 continue outer; 663 } 664 } 665 throw FunUtil.newEvalException( 666 null, 667 "Infinite loop while evaluating calculated member '" + 668 eval.expandingMember + "'; context stack is " + 669 eval.getContextString()); 670 } 671 } 672 673 private String getContextString() { 674 final boolean skipDefaultMembers = true; 675 final StringBuilder buf = new StringBuilder("{"); 676 int frameCount = 0; 677 for (RolapEvaluator eval = this; eval != null; 678 eval = (RolapEvaluator) eval.getParent()) { 679 if (eval.expandingMember == null) { 680 continue; 681 } 682 if (frameCount++ > 0) { 683 buf.append(", "); 684 } 685 buf.append("("); 686 int memberCount = 0; 687 for (Member m : eval.currentMembers) { 688 if (skipDefaultMembers && 689 m == m.getHierarchy().getDefaultMember()) { 690 continue; 691 } 692 if (memberCount++ > 0) { 693 buf.append(", "); 694 } 695 buf.append(m.getUniqueName()); 696 } 697 buf.append(")"); 698 } 699 buf.append("}"); 700 return buf.toString(); 701 } 702 703 public final Object getProperty(String name, Object defaultValue) { 704 Object o = defaultValue; 705 int maxSolve = Integer.MIN_VALUE; 706 for (int i = 0; i < currentMembers.length; i++) { 707 final Member member = currentMembers[i]; 708 709 // more than one usage 710 if (member == null) { 711 if (getLogger().isDebugEnabled()) { 712 getLogger().debug( 713 "RolapEvaluator.getProperty: member == null " 714 + " , count=" + i); 715 } 716 continue; 717 } 718 719 final Object p = member.getPropertyValue(name); 720 if (p != null) { 721 final int solve = member.getSolveOrder(); 722 if (solve > maxSolve) { 723 o = p; 724 maxSolve = solve; 725 } 726 } 727 } 728 return o; 729 } 730 731 /** 732 * Returns the format string for this cell. This is computed by evaluating 733 * the format expression in the current context, and therefore different 734 * cells may have different format strings. 735 * 736 * @post return != null 737 */ 738 public final String getFormatString() { 739 final Exp formatExp = 740 (Exp) getProperty(Property.FORMAT_EXP.name, null); 741 if (formatExp == null) { 742 return "Standard"; 743 } 744 final Calc formatCalc = root.getCompiled(formatExp, true, null); 745 final Object o = formatCalc.evaluate(this); 746 if (o == null) { 747 return "Standard"; 748 } 749 return o.toString(); 750 } 751 752 private Format getFormat() { 753 final String formatString = getFormatString(); 754 return getFormat(formatString); 755 } 756 757 private Format getFormat(String formatString) { 758 return Format.get(formatString, root.connection.getLocale()); 759 } 760 761 public final Locale getConnectionLocale() { 762 return root.connection.getLocale(); 763 } 764 765 public final String format(Object o) { 766 if (o == Util.nullValue) { 767 Format format = getFormat(); 768 return format.format(null); 769 } else if (o instanceof Throwable) { 770 return "#ERR: " + o.toString(); 771 } else if (o instanceof String) { 772 return (String) o; 773 } else { 774 Format format = getFormat(); 775 return format.format(o); 776 } 777 } 778 779 public final String format(Object o, String formatString) { 780 if (o == Util.nullValue) { 781 Format format = getFormat(formatString); 782 return format.format(null); 783 } else if (o instanceof Throwable) { 784 return "#ERR: " + o.toString(); 785 } else if (o instanceof String) { 786 return (String) o; 787 } else { 788 Format format = getFormat(formatString); 789 return format.format(o); 790 } 791 } 792 793 /** 794 * Creates a key which uniquely identifes an expression and its 795 * context. The context includes members of dimensions which the 796 * expression is dependent upon. 797 */ 798 private Object getExpResultCacheKey(ExpCacheDescriptor descriptor) { 799 final List<Object> key = new ArrayList<Object>(); 800 key.add(descriptor.getExp()); 801 final int[] dimensionOrdinals = 802 descriptor.getDependentDimensionOrdinals(); 803 for (int i = 0; i < dimensionOrdinals.length; i++) { 804 final int dimensionOrdinal = dimensionOrdinals[i]; 805 final Member member = currentMembers[dimensionOrdinal]; 806 807 // more than one usage 808 if (member == null) { 809 getLogger().debug( 810 "RolapEvaluator.getExpResultCacheKey: " + 811 "member == null; dimensionOrdinal=" + i); 812 continue; 813 } 814 815 key.add(member); 816 } 817 return key; 818 } 819 820 public final Object getCachedResult(ExpCacheDescriptor cacheDescriptor) { 821 // Look up a cached result, and if not present, compute one and add to 822 // cache. Use a dummy value to represent nulls. 823 final Object key = getExpResultCacheKey(cacheDescriptor); 824 Object result = root.getCacheResult(key); 825 if (result == null) { 826 boolean aggCacheDirty = cellReader.isDirty(); 827 int aggregateCacheMissCountBefore = cellReader.getMissCount(); 828 result = cacheDescriptor.evaluate(this); 829 int aggregateCacheMissCountAfter = cellReader.getMissCount(); 830 831 boolean isValidResult; 832 833 if (!aggCacheDirty && 834 (aggregateCacheMissCountBefore == aggregateCacheMissCountAfter)) { 835 // Cache the evaluation result as valid result if the 836 // evaluation did not use any missing aggregates. Missing aggregates 837 // could be used when aggregate cache is not fully loaded, or if 838 // new missing aggregates are seen. 839 isValidResult = true; 840 } else { 841 // Cache the evaluation result as invalid result if the 842 // evaluation uses missing aggregates. 843 isValidResult = false; 844 } 845 root.putCacheResult( 846 key, 847 result == null ? nullResult : result, 848 isValidResult); 849 } else if (result == nullResult) { 850 result = null; 851 } 852 853 return result; 854 } 855 856 public final void clearExpResultCache(boolean clearValidResult) { 857 root.clearResultCache(clearValidResult); 858 } 859 860 public final boolean isNonEmpty() { 861 return nonEmpty; 862 } 863 864 public final void setNonEmpty(boolean nonEmpty) { 865 this.nonEmpty = nonEmpty; 866 } 867 868 public final RuntimeException newEvalException(Object context, String s) { 869 return FunUtil.newEvalException((FunDef) context, s); 870 } 871 872 public final Object evaluateNamedSet(String name, Exp exp) { 873 return root.evaluateNamedSet(name, exp); 874 } 875 876 public final int getMissCount() { 877 return cellReader.getMissCount(); 878 } 879 880 public final Object getParameterValue(ParameterSlot slot) { 881 return root.getParameterValue(slot); 882 } 883 884 final void addCalcMember(Member member) { 885 assert member != null; 886 assert member.isCalculated(); 887 calcMembers[calcMemberCount++] = member; 888 } 889 890 private Member peekCalcMember() { 891 switch (calcMemberCount) { 892 case 0: 893 return null; 894 895 case 1: 896 return calcMembers[0]; 897 898 default: 899 // TODO Consider revising employing the Strategy architectural pattern 900 // for setting up solve order mode handling. 901 902 switch (solveOrderMode) { 903 case ABSOLUTE: 904 return getAbsoluteMaxSolveOrder(calcMembers); 905 case SCOPED: 906 return getScopedMaxSolveOrder(calcMembers); 907 default: 908 throw Util.unexpected(solveOrderMode); 909 } 910 } 911 } 912 913 /* 914 * Returns the member with the highest solve order according to AS2000 rules. 915 * This was the behavior prior to solve order mode being configurable. 916 * 917 * <p>The SOLVE_ORDER value is absolute regardless of where it is defined; 918 * e.g. a query defined calculated member with a SOLVE_ORDER of 1 always takes 919 * precedence over a cube defined value of 2. 920 * 921 * <p>No special consideration is given to the aggregate function. 922 */ 923 private Member getAbsoluteMaxSolveOrder(Member [] calcMembers) { 924 // Find member with the highest solve order. 925 Member maxSolveMember = calcMembers[0]; 926 int maxSolve = maxSolveMember.getSolveOrder(); 927 for (int i = 1; i < calcMemberCount; i++) { 928 Member member = calcMembers[i]; 929 int solve = member.getSolveOrder(); 930 if (solve >= maxSolve) { 931 // If solve orders tie, the dimension with the lower 932 // ordinal wins. 933 if (solve > maxSolve 934 || member.getDimension().getOrdinal(root.cube) 935 < maxSolveMember.getDimension().getOrdinal(root.cube)) { 936 maxSolve = solve; 937 maxSolveMember = member; 938 } 939 } 940 } 941 942 return maxSolveMember; 943 } 944 945 /* 946 * Returns the member with the highest solve order according to AS2005 947 * scoping rules. 948 * 949 * <p>By default, cube calculated members are resolved before any session 950 * scope calculated members, and session scope members are resolved before 951 * any query defined calculation. The SOLVE_ORDER value only applies within 952 * the scope in which it was defined. 953 * 954 * <p>The aggregate function is always applied to base members; i.e. as if 955 * SOLVE_ORDER was defined to be the lowest value in a given evaluation in a 956 * SSAS2000 sense. 957 */ 958 private Member getScopedMaxSolveOrder(Member [] calcMembers) { 959 960 // Finite state machine that determines the member with the highest 961 // solve order. 962 Member maxSolveMember = null; 963 ScopedMaxSolveOrderFinderState state = 964 ScopedMaxSolveOrderFinderState.START; 965 for (int i = 0; i < calcMemberCount; i++) { 966 Member member = calcMembers[i]; 967 switch (state) { 968 case START: 969 maxSolveMember = member; 970 if (foundAggregateFunction(maxSolveMember.getExpression())) { 971 state = ScopedMaxSolveOrderFinderState.AGG_SCOPE; 972 } else if (maxSolveMember.isCalculatedInQuery()) { 973 state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; 974 } else { 975 state = ScopedMaxSolveOrderFinderState.CUBE_SCOPE; 976 } 977 break; 978 979 case AGG_SCOPE: 980 if (foundAggregateFunction(member.getExpression())) { 981 if (member.getSolveOrder() > maxSolveMember.getSolveOrder() || 982 (member.getSolveOrder() == maxSolveMember.getSolveOrder() && 983 member.getDimension().getOrdinal(root.cube) < 984 maxSolveMember.getDimension().getOrdinal(root.cube))) { 985 maxSolveMember = member; 986 } 987 } else if (member.isCalculatedInQuery()) { 988 maxSolveMember = member; 989 state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; 990 } else { 991 maxSolveMember = member; 992 state = ScopedMaxSolveOrderFinderState.CUBE_SCOPE; 993 } 994 break; 995 996 case CUBE_SCOPE: 997 if (foundAggregateFunction(member.getExpression())) { 998 continue; 999 } 1000 1001 if (member.isCalculatedInQuery()) { 1002 maxSolveMember = member; 1003 state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; 1004 } else if (member.getSolveOrder() > maxSolveMember.getSolveOrder() || 1005 (member.getSolveOrder() == maxSolveMember.getSolveOrder() && 1006 member.getDimension().getOrdinal(root.cube) < 1007 maxSolveMember.getDimension().getOrdinal(root.cube))) { 1008 1009 maxSolveMember = member; 1010 } 1011 break; 1012 1013 case QUERY_SCOPE: 1014 if (foundAggregateFunction(member.getExpression())) { 1015 continue; 1016 } 1017 1018 if (member.isCalculatedInQuery()) { 1019 if (member.getSolveOrder() > maxSolveMember.getSolveOrder() || 1020 (member.getSolveOrder() == maxSolveMember.getSolveOrder() && 1021 member.getDimension().getOrdinal(root.cube) < 1022 maxSolveMember.getDimension().getOrdinal(root.cube))) { 1023 maxSolveMember = member; 1024 } 1025 } 1026 break; 1027 } 1028 } 1029 1030 return maxSolveMember; 1031 } 1032 1033 private boolean foundAggregateFunction(Exp exp) { 1034 if (exp instanceof ResolvedFunCall) { 1035 ResolvedFunCall resolvedFunCall = (ResolvedFunCall) exp; 1036 if (resolvedFunCall.getFunDef() instanceof AggregateFunDef) { 1037 return true; 1038 } else { 1039 for (Exp argExp : resolvedFunCall.getArgs()) { 1040 if (foundAggregateFunction(argExp)) { 1041 return true; 1042 } 1043 } 1044 } 1045 } 1046 return false; 1047 } 1048 1049 private void removeCalcMember(Member previous) { 1050 for (int i = 0; i < calcMemberCount; i++) { 1051 final Member calcMember = calcMembers[i]; 1052 if (calcMember == previous) { 1053 // overwrite this member with the end member 1054 --calcMemberCount; 1055 calcMembers[i] = calcMembers[calcMemberCount]; 1056 calcMembers[calcMemberCount] = null; // to allow gc 1057 } 1058 } 1059 } 1060 1061 public final int getIterationLength() { 1062 return iterationLength; 1063 } 1064 1065 public final void setIterationLength(int length) { 1066 iterationLength = length; 1067 } 1068 1069 public final boolean isEvalAxes() { 1070 return evalAxes; 1071 } 1072 1073 public final void setEvalAxes(boolean evalAxes) { 1074 this.evalAxes = evalAxes; 1075 } 1076 1077 /** 1078 * Checks if unrelated dimensions to the measure in the current context 1079 * should be ignored. 1080 * @return boolean 1081 */ 1082 public boolean shouldIgnoreUnrelatedDimensions() { 1083 return getCube(). 1084 shouldIgnoreUnrelatedDimensions(getMeasureCube().getName()); 1085 } 1086 } 1087 1088 // End RolapEvaluator.java