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) 2001-2005 Julian Hyde 008// Copyright (C) 2005-2012 Pentaho and others 009// All Rights Reserved. 010// 011// jhyde, 10 August, 2001 012*/ 013package mondrian.rolap; 014 015import mondrian.calc.Calc; 016import mondrian.calc.ParameterSlot; 017import mondrian.olap.*; 018import mondrian.olap.fun.FunUtil; 019import mondrian.server.Statement; 020import mondrian.spi.Dialect; 021import mondrian.util.Format; 022 023import org.apache.log4j.Logger; 024 025import java.util.*; 026 027/** 028 * <code>RolapEvaluator</code> evaluates expressions in a dimensional 029 * environment. 030 * 031 * <p>The context contains a member (which may be the default member) 032 * for every dimension in the current cube. Certain operations, such as 033 * evaluating a calculated member or a tuple, change the current context. 034 * 035 * <p>There are two ways of preserving context. 036 * 037 * <p>First, the {@link #push} 038 * method creates a verbatim copy of the evaluator. Use that copy for 039 * computations, and any changes of state will be made only to the copy. 040 * 041 * <p>Second, the {@link #savepoint()} method tells the evaluator to create a 042 * checkpoint of its state, and returns an {@code int} value that can later be 043 * passed to {@link #restore(int)}. 044 * 045 * <p>The {@code savepoint} method is recommended for most purposes, because the 046 * initial checkpoint is extremely cheap. Each call that modifies state (such as 047 * {@link mondrian.olap.Evaluator#setContext(mondrian.olap.Member)}) creates, at 048 * a modest cost, an entry on an internal command stack. 049 * 050 * <p>One occasion that you would use {@code push} is when creating an 051 * iterator, and the iterator needs its own evaluator context, even if the 052 * code that created the iterator later reverts the context. In this case, 053 * the iterator's constructor should call {@code push}. 054 * 055 * <h3>Developers note</h3> 056 * 057 * <p>Many of the methods in this class are performance-critical. Where 058 * possible they are declared 'final' so that the JVM can optimize calls to 059 * these methods. If future functionality requires it, the 'final' modifier 060 * can be removed and these methods can be overridden. 061 * 062 * @author jhyde 063 * @since 10 August, 2001 064 */ 065public class RolapEvaluator implements Evaluator { 066 private static final Logger LOGGER = Logger.getLogger(RolapEvaluator.class); 067 068 /** 069 * Dummy value to represent null results in the expression cache. 070 */ 071 private static final Object nullResult = new Object(); 072 073 private final RolapMember[] currentMembers; 074 private final RolapEvaluator parent; 075 protected CellReader cellReader; 076 private final int ancestorCommandCount; 077 078 private Member expandingMember; 079 private boolean firstExpanding; 080 private boolean nonEmpty; 081 protected final RolapEvaluatorRoot root; 082 private int iterationLength; 083 private boolean evalAxes; 084 085 private final RolapCalculation[] calculations; 086 private int calculationCount; 087 088 /** 089 * List of lists of tuples or members, rarely used, but overrides the 090 * ordinary dimensional context if set when a cell value comes to be 091 * evaluated. 092 */ 093 protected final List<List<List<Member>>> aggregationLists; 094 095 private final List<Member> slicerMembers; 096 private boolean nativeEnabled; 097 private Member[] nonAllMembers; 098 private int commandCount; 099 private Object[] commands; 100 101 /** 102 * Set of expressions actively being expanded. Prevents infinite cycle of 103 * expansions. 104 * 105 * @return Mutable set of expressions being expanded 106 */ 107 public Set<Exp> getActiveNativeExpansions() { 108 return root.activeNativeExpansions; 109 } 110 111 /** 112 * States of the finite state machine for determining the max solve order 113 * for the "scoped" behavior. 114 */ 115 private enum ScopedMaxSolveOrderFinderState { 116 START, 117 AGG_SCOPE, 118 CUBE_SCOPE, 119 QUERY_SCOPE 120 } 121 122 /** 123 * Creates a non-root evaluator. 124 * 125 * @param root Root context for stack of evaluators (contains information 126 * which does not change during the evaluation) 127 * @param parent Parent evaluator, not null 128 * @param aggregationList List of tuples to add to aggregation context, 129 * or null 130 */ 131 protected RolapEvaluator( 132 RolapEvaluatorRoot root, 133 RolapEvaluator parent, 134 List<List<Member>> aggregationList) 135 { 136 this.iterationLength = 1; 137 this.root = root; 138 assert parent != null; 139 this.parent = parent; 140 141 ancestorCommandCount = 142 parent.ancestorCommandCount + parent.commandCount; 143 nonEmpty = parent.nonEmpty; 144 nativeEnabled = parent.nativeEnabled; 145 evalAxes = parent.evalAxes; 146 cellReader = parent.cellReader; 147 currentMembers = parent.currentMembers.clone(); 148 calculations = parent.calculations.clone(); 149 calculationCount = parent.calculationCount; 150 slicerMembers = new ArrayList<Member>(parent.slicerMembers); 151 152 commands = new Object[10]; 153 commands[0] = Command.SAVEPOINT; // sentinel 154 commandCount = 1; 155 156 // Build aggregationLists, combining parent's aggregationLists (if not 157 // null) and the new aggregation list (if any). 158 List<List<List<Member>>> aggregationLists = null; 159 if (parent.aggregationLists != null) { 160 aggregationLists = 161 new ArrayList<List<List<Member>>>(parent.aggregationLists); 162 } 163 if (aggregationList != null) { 164 if (aggregationLists == null) { 165 aggregationLists = new ArrayList<List<List<Member>>>(); 166 } 167 aggregationLists.add(aggregationList); 168 List<Member> tuple = aggregationList.get(0); 169 for (Member member : tuple) { 170 setContext(member.getHierarchy().getAllMember()); 171 } 172 } 173 this.aggregationLists = aggregationLists; 174 175 expandingMember = parent.expandingMember; 176 } 177 178 /** 179 * Creates a root evaluator. 180 * 181 * @param root Shared context between this evaluator and its children 182 */ 183 public RolapEvaluator(RolapEvaluatorRoot root) { 184 this.iterationLength = 1; 185 this.root = root; 186 this.parent = null; 187 ancestorCommandCount = 0; 188 nonEmpty = false; 189 nativeEnabled = 190 MondrianProperties.instance().EnableNativeNonEmpty.get() 191 || MondrianProperties.instance().EnableNativeCrossJoin.get(); 192 evalAxes = false; 193 cellReader = null; 194 currentMembers = root.defaultMembers.clone(); 195 calculations = new RolapCalculation[currentMembers.length]; 196 calculationCount = 0; 197 slicerMembers = new ArrayList<Member>(); 198 aggregationLists = null; 199 200 commands = new Object[10]; 201 commands[0] = Command.SAVEPOINT; // sentinel 202 commandCount = 1; 203 204 for (RolapMember member : currentMembers) { 205 if (member.isEvaluated()) { 206 addCalculation(member, true); 207 } 208 } 209 210 // we expect client to set CellReader 211 } 212 213 /** 214 * Creates an evaluator. 215 */ 216 public static Evaluator create(Statement statement) { 217 final RolapEvaluatorRoot root = new RolapEvaluatorRoot(statement); 218 return new RolapEvaluator(root); 219 } 220 221 public RolapCube getMeasureCube() { 222 final RolapMember measure = currentMembers[0]; 223 if (measure instanceof RolapStoredMeasure) { 224 return ((RolapStoredMeasure) measure).getCube(); 225 } 226 return null; 227 } 228 229 public boolean mightReturnNullForUnrelatedDimension() { 230 if (!MondrianProperties.instance() 231 .IgnoreMeasureForNonJoiningDimension.get()) 232 { 233 return false; 234 } 235 RolapCube virtualCube = getCube(); 236 return virtualCube.isVirtual(); 237 } 238 239 public boolean needToReturnNullForUnrelatedDimension(Member[] members) { 240 assert mightReturnNullForUnrelatedDimension() 241 : "Should not even call this method if nulls are impossible"; 242 RolapCube baseCube = getMeasureCube(); 243 if (baseCube == null) { 244 return false; 245 } 246 RolapCube virtualCube = getCube(); 247 if (virtualCube.shouldIgnoreUnrelatedDimensions(baseCube.getName())) { 248 return false; 249 } 250 Set<Dimension> nonJoiningDimensions = 251 baseCube.nonJoiningDimensions(members); 252 return !nonJoiningDimensions.isEmpty(); 253 } 254 255 public boolean nativeEnabled() { 256 return nativeEnabled; 257 } 258 259 public boolean currentIsEmpty() { 260 // If a cell evaluates to null, it is always deemed empty. 261 Object o = evaluateCurrent(); 262 if (o == Util.nullValue || o == null) { 263 return true; 264 } 265 final RolapCube measureCube = getMeasureCube(); 266 if (measureCube == null) { 267 return false; 268 } 269 // For other cell values (e.g. zero), the cell is deemed empty if the 270 // number of fact table rows is zero. 271 final int savepoint = savepoint(); 272 try { 273 setContext(measureCube.getFactCountMeasure()); 274 o = evaluateCurrent(); 275 } finally { 276 restore(savepoint); 277 } 278 return o == null 279 || (o instanceof Number && ((Number) o).intValue() == 0); 280 } 281 282 public Member getPreviousContext(Hierarchy hierarchy) { 283 for (RolapEvaluator e = this; e != null; e = e.parent) { 284 for (int i = commandCount - 1; i > 0;) { 285 Command command = (Command) commands[i]; 286 if (command == Command.SET_CONTEXT) { 287 return (Member) commands[i - 2]; 288 } 289 i -= command.width; 290 } 291 } 292 return null; 293 } 294 295 public final QueryTiming getTiming() { 296 return root.execution.getQueryTiming(); 297 } 298 299 public final int savepoint() { 300 final int commandCount1 = commandCount; 301 if (commands[commandCount - 1] == Command.SAVEPOINT) { 302 // Already at a save point; no need to create another. 303 return commandCount1; 304 } 305 306 // enough room for CHECKSUM command, if asserts happen to be enabled 307 ensureCommandCapacity(commandCount + 3); 308 commands[commandCount++] = Command.SAVEPOINT; 309 //noinspection AssertWithSideEffects 310 assert !Util.DEBUG || addChecksumStateCommand(); 311 return commandCount1; 312 } 313 314 public final void setNativeEnabled(boolean nativeEnabled) { 315 if (nativeEnabled != this.nativeEnabled) { 316 ensureCommandCapacity(commandCount + 2); 317 commands[commandCount++] = this.nativeEnabled; 318 commands[commandCount++] = Command.SET_NATIVE_ENABLED; 319 this.nativeEnabled = nativeEnabled; 320 } 321 } 322 323 protected final Logger getLogger() { 324 return LOGGER; 325 } 326 327 public final Member[] getMembers() { 328 return currentMembers; 329 } 330 331 public final Member[] getNonAllMembers() { 332 if (nonAllMembers == null) { 333 nonAllMembers = new RolapMember[root.nonAllPositionCount]; 334 for (int i = 0; i < root.nonAllPositionCount; i++) { 335 int nonAllPosition = root.nonAllPositions[i]; 336 nonAllMembers[i] = currentMembers[nonAllPosition]; 337 } 338 } 339 return nonAllMembers; 340 } 341 342 public final List<List<List<Member>>> getAggregationLists() { 343 return aggregationLists; 344 } 345 346 final void setCellReader(CellReader cellReader) { 347 if (cellReader != this.cellReader) { 348 ensureCommandCapacity(commandCount + 2); 349 commands[commandCount++] = this.cellReader; 350 commands[commandCount++] = Command.SET_CELL_READER; 351 this.cellReader = cellReader; 352 } 353 } 354 355 public final RolapCube getCube() { 356 return root.cube; 357 } 358 359 public final Query getQuery() { 360 return root.query; 361 } 362 363 public final int getDepth() { 364 return 0; 365 } 366 367 public final RolapEvaluator getParent() { 368 return parent; 369 } 370 371 public final SchemaReader getSchemaReader() { 372 return root.schemaReader; 373 } 374 375 public Date getQueryStartTime() { 376 return root.getQueryStartTime(); 377 } 378 379 public Dialect getDialect() { 380 return root.currentDialect; 381 } 382 383 public final RolapEvaluator push(Member[] members) { 384 final RolapEvaluator evaluator = _push(null); 385 evaluator.setContext(members); 386 return evaluator; 387 } 388 389 public final RolapEvaluator push(Member member) { 390 final RolapEvaluator evaluator = _push(null); 391 evaluator.setContext(member); 392 return evaluator; 393 } 394 395 public final Evaluator push(boolean nonEmpty) { 396 final RolapEvaluator evaluator = _push(null); 397 evaluator.setNonEmpty(nonEmpty); 398 return evaluator; 399 } 400 401 public final Evaluator push(boolean nonEmpty, boolean nativeEnabled) { 402 final RolapEvaluator evaluator = _push(null); 403 evaluator.setNonEmpty(nonEmpty); 404 evaluator.setNativeEnabled(nativeEnabled); 405 return evaluator; 406 } 407 408 public final RolapEvaluator push() { 409 return _push(null); 410 } 411 412 private void ensureCommandCapacity(int minCapacity) { 413 if (minCapacity > commands.length) { 414 int newCapacity = commands.length * 2; 415 if (newCapacity < minCapacity) { 416 newCapacity = minCapacity; 417 } 418 commands = Util.copyOf(commands, newCapacity); 419 } 420 } 421 422 /** 423 * Adds a command to the stack that ensures that the state after restoring 424 * is the same as the current state. 425 * 426 * <p>Returns true so that can conveniently be called from 'assert'. 427 * 428 * @return true 429 */ 430 private boolean addChecksumStateCommand() { 431 // assume that caller has checked that command array is large enough 432 commands[commandCount++] = checksumState(); 433 commands[commandCount++] = Command.CHECKSUM; 434 return true; 435 } 436 437 /** 438 * Creates a clone of the current validator. 439 * 440 * @param aggregationList List of tuples to add to aggregation context, 441 * or null 442 */ 443 protected RolapEvaluator _push(List<List<Member>> aggregationList) { 444 root.execution.checkCancelOrTimeout(); 445 return new RolapEvaluator(root, this, aggregationList); 446 } 447 448 public final void restore(int savepoint) { 449 while (commandCount > savepoint) { 450 ((Command) commands[--commandCount]).execute(this); 451 } 452 } 453 454 public final Evaluator pushAggregation(List<List<Member>> list) { 455 return _push(list); 456 } 457 458 /** 459 * Returns true if the other object is a {@link RolapEvaluator} with 460 * identical context. 461 */ 462 public final boolean equals(Object obj) { 463 if (!(obj instanceof RolapEvaluator)) { 464 return false; 465 } 466 RolapEvaluator that = (RolapEvaluator) obj; 467 return Arrays.equals(this.currentMembers, that.currentMembers); 468 } 469 470 public final int hashCode() { 471 return Util.hashArray(0, this.currentMembers); 472 } 473 474 /** 475 * Adds a slicer member to the evaluator context, and remember it as part 476 * of the slicer. The slicer members are passed onto derived evaluators 477 * so that functions using those evaluators can choose to ignore the 478 * slicer members. One such function is CrossJoin emptiness check. 479 * 480 * @param member a member in the slicer 481 */ 482 public final void setSlicerContext(Member member) { 483 setContext(member); 484 slicerMembers.add(member); 485 } 486 487 /** 488 * Return the list of slicer members in the current evaluator context. 489 * @return slicerMembers 490 */ 491 public final List<Member> getSlicerMembers() { 492 return slicerMembers; 493 } 494 495 public final Member setContext(Member member) { 496 // Note: the body of this function is identical to calling 497 // 'setContext(member, true)'. We inline the logic for performance. 498 499 final RolapMemberBase m = (RolapMemberBase) member; 500 final int ordinal = m.getHierarchy().getOrdinalInCube(); 501 final RolapMember previous = currentMembers[ordinal]; 502 503 // If the context is unchanged, save ourselves some effort. It would be 504 // a mistake to use equals here; we might treat the visual total member 505 // 'Gender.All' the same as the true 'Gender.All' because they have the 506 // same unique name, and that would be wrong. 507 if (m == previous) { 508 return previous; 509 } 510 // We call 'exists' before 'removeCalcMember' for efficiency. 511 // 'exists' has a smaller stack to search before 'removeCalcMember' 512 // adds an 'ADD_CALCULATION' command. 513 if (!exists(ordinal)) { 514 ensureCommandCapacity(commandCount + 3); 515 commands[commandCount++] = previous; 516 commands[commandCount++] = ordinal; 517 commands[commandCount++] = Command.SET_CONTEXT; 518 } 519 if (previous.isEvaluated()) { 520 removeCalculation(previous, false); 521 } 522 currentMembers[ordinal] = m; 523 if (previous.isAll() && !m.isAll() && isNewPosition(ordinal)) { 524 root.nonAllPositions[root.nonAllPositionCount] = ordinal; 525 root.nonAllPositionCount++; 526 } 527 if (m.isEvaluated()) { 528 addCalculation(m, false); 529 } 530 nonAllMembers = null; 531 return previous; 532 } 533 534 public final void setContext(Member member, boolean safe) { 535 final RolapMemberBase m = (RolapMemberBase) member; 536 final int ordinal = m.getHierarchy().getOrdinalInCube(); 537 final RolapMember previous = currentMembers[ordinal]; 538 539 // If the context is unchanged, save ourselves some effort. It would be 540 // a mistake to use equals here; we might treat the visual total member 541 // 'Gender.All' the same as the true 'Gender.All' because they have the 542 // same unique name, and that would be wrong. 543 if (m == previous) { 544 return; 545 } 546 if (safe) { 547 // We call 'exists' before 'removeCalcMember' for efficiency. 548 // 'exists' has a smaller stack to search before 'removeCalcMember' 549 // adds an 'ADD_CALCULATION' command. 550 if (!exists(ordinal)) { 551 ensureCommandCapacity(commandCount + 3); 552 commands[commandCount++] = previous; 553 commands[commandCount++] = ordinal; 554 commands[commandCount++] = Command.SET_CONTEXT; 555 } 556 } 557 if (previous.isEvaluated()) { 558 removeCalculation(previous, false); 559 } 560 currentMembers[ordinal] = m; 561 if (previous.isAll() && !m.isAll() && isNewPosition(ordinal)) { 562 root.nonAllPositions[root.nonAllPositionCount] = ordinal; 563 root.nonAllPositionCount++; 564 } 565 if (m.isEvaluated()) { 566 addCalculation(m, false); 567 } 568 nonAllMembers = null; 569 } 570 571 /** 572 * Returns whether a member of the hierarchy with a given ordinal has been 573 * preserved on the stack since the last savepoint. 574 * 575 * @param ordinal Hierarchy ordinal 576 * @return Whether there is a member with the given hierarchy ordinal on 577 * the stack 578 */ 579 private boolean exists(int ordinal) { 580 for (int i = commandCount - 1;;) { 581 final Command command = (Command) commands[i]; 582 switch (command) { 583 case SAVEPOINT: 584 return false; 585 case SET_CONTEXT: 586 final Integer memberOrdinal = (Integer) commands[i - 1]; 587 if (ordinal == memberOrdinal) { 588 return true; 589 } 590 break; 591 } 592 i -= command.width; 593 } 594 } 595 596 private boolean isNewPosition(int ordinal) { 597 for (int nonAllPosition : root.nonAllPositions) { 598 if (ordinal == nonAllPosition) { 599 return false; 600 } 601 } 602 return true; 603 } 604 605 public final void setContext(List<Member> memberList) { 606 for (int i = 0, n = memberList.size(); i < n; i++) { 607 Member member = memberList.get(i); 608 assert member != null : "null member in " + memberList; 609 setContext(member); 610 } 611 } 612 613 public final void setContext(List<Member> memberList, boolean safe) { 614 for (int i = 0, n = memberList.size(); i < n; i++) { 615 Member member = memberList.get(i); 616 assert member != null : "null member in " + memberList; 617 setContext(member, safe); 618 } 619 } 620 621 public final void setContext(Member[] members) { 622 for (int i = 0, length = members.length; i < length; i++) { 623 Member member = members[i]; 624 assert member != null 625 : "null member in " + Arrays.toString(members); 626 setContext(member); 627 } 628 } 629 630 public final void setContext(Member[] members, boolean safe) { 631 for (int i = 0, length = members.length; i < length; i++) { 632 Member member = members[i]; 633 assert member != null : Arrays.asList(members); 634 setContext(member, safe); 635 } 636 } 637 638 public final RolapMember getContext(Hierarchy hierarchy) { 639 return currentMembers[((RolapHierarchy) hierarchy).getOrdinalInCube()]; 640 } 641 642 /** 643 * More specific version of {@link #getContext(mondrian.olap.Hierarchy)}, 644 * for internal code. 645 * 646 * @param hierarchy Hierarchy 647 * @return current member 648 */ 649 public final RolapMember getContext(RolapHierarchy hierarchy) { 650 return currentMembers[hierarchy.getOrdinalInCube()]; 651 } 652 653 public final Object evaluateCurrent() { 654 // Get the member in the current context which is (a) calculated, and 655 // (b) has the highest solve order. If there are no calculated members, 656 // go ahead and compute the cell. 657 RolapCalculation maxSolveMember; 658 switch (calculationCount) { 659 case 0: 660 final Object o = cellReader.get(this); 661 if (o == Util.nullValue) { 662 return null; 663 } 664 return o; 665 666 case 1: 667 maxSolveMember = calculations[0]; 668 break; 669 670 default: 671 switch (root.solveOrderMode) { 672 case ABSOLUTE: 673 maxSolveMember = getAbsoluteMaxSolveOrder(); 674 break; 675 case SCOPED: 676 maxSolveMember = getScopedMaxSolveOrder(); 677 break; 678 default: 679 throw Util.unexpected(root.solveOrderMode); 680 } 681 } 682 final int savepoint = savepoint(); 683 maxSolveMember.setContextIn(this); 684 final Calc calc = maxSolveMember.getCompiledExpression(root); 685 final Object o; 686 try { 687 o = calc.evaluate(this); 688 } finally { 689 restore(savepoint); 690 } 691 if (o == Util.nullValue) { 692 return null; 693 } 694 return o; 695 } 696 697 void setExpanding(Member member) { 698 assert member != null; 699 ensureCommandCapacity(commandCount + 3); 700 commands[commandCount++] = this.expandingMember; 701 commands[commandCount++] = this.firstExpanding; 702 commands[commandCount++] = Command.SET_EXPANDING; 703 expandingMember = member; 704 firstExpanding = true; // REVIEW: is firstExpanding used? 705 706 final int totalCommandCount = commandCount + ancestorCommandCount; 707 if (totalCommandCount > root.recursionCheckCommandCount) { 708 checkRecursion(this, commandCount - 4); 709 710 // Set the threshold where we will next check for infinite 711 // recursion. 712 root.recursionCheckCommandCount = 713 totalCommandCount + (root.defaultMembers.length << 4); 714 } 715 } 716 717 /** 718 * Returns the calculated member being currently expanded. 719 * 720 * <p>This can be useful if many calculated members are generated with 721 * essentially the same expression. The compiled expression can call this 722 * method to find which instance of the member is current, and therefore the 723 * calculated members can share the same {@link Calc} object. 724 * 725 * @return Calculated member currently being expanded 726 */ 727 Member getExpanding() { 728 return expandingMember; 729 } 730 731 /** 732 * Makes sure that there is no evaluator with identical context on the 733 * stack. 734 * 735 * @param eval Evaluator 736 * @throws mondrian.olap.fun.MondrianEvaluationException if there is a loop 737 */ 738 private static void checkRecursion(RolapEvaluator eval, int c) { 739 RolapMember[] members = eval.currentMembers.clone(); 740 Member expanding = eval.expandingMember; 741 742 // Find an ancestor evaluator that has identical context to this one: 743 // same member context, and expanding the same calculation. 744 while (true) { 745 if (c < 0) { 746 eval = eval.parent; 747 if (eval == null) { 748 return; 749 } 750 c = eval.commandCount - 1; 751 } else { 752 Command command = (Command) eval.commands[c]; 753 switch (command) { 754 case SET_CONTEXT: 755 int memberOrdinal = (Integer) eval.commands[c - 1]; 756 RolapMember member = (RolapMember) eval.commands[c - 2]; 757 members[memberOrdinal] = member; 758 break; 759 case SET_EXPANDING: 760 expanding = (RolapMember) eval.commands[c - 2]; 761 if (Arrays.equals(members, eval.currentMembers) 762 && expanding == eval.expandingMember) 763 { 764 throw FunUtil.newEvalException( 765 null, 766 "Infinite loop while evaluating calculated member '" 767 + eval.expandingMember + "'; context stack is " 768 + eval.getContextString()); 769 } 770 } 771 c -= command.width; 772 } 773 } 774 } 775 776 private String getContextString() { 777 RolapMember[] members = currentMembers.clone(); 778 final boolean skipDefaultMembers = true; 779 final StringBuilder buf = new StringBuilder("{"); 780 int frameCount = 0; 781 boolean changedSinceLastSavepoint = false; 782 for (RolapEvaluator eval = this; eval != null; eval = eval.parent) { 783 if (eval.expandingMember == null) { 784 continue; 785 } 786 for (int c = eval.commandCount - 1; c > 0;) { 787 Command command = (Command) eval.commands[c]; 788 switch (command) { 789 case SAVEPOINT: 790 if (changedSinceLastSavepoint) { 791 if (frameCount++ > 0) { 792 buf.append(", "); 793 } 794 buf.append("("); 795 int memberCount = 0; 796 for (Member m : members) { 797 if (skipDefaultMembers 798 && m == m.getHierarchy().getDefaultMember()) 799 { 800 continue; 801 } 802 if (memberCount++ > 0) { 803 buf.append(", "); 804 } 805 buf.append(m.getUniqueName()); 806 } 807 buf.append(")"); 808 } 809 changedSinceLastSavepoint = false; 810 break; 811 case SET_CONTEXT: 812 changedSinceLastSavepoint = true; 813 int memberOrdinal = (Integer) eval.commands[c - 1]; 814 RolapMember member = (RolapMember) eval.commands[c - 2]; 815 members[memberOrdinal] = member; 816 break; 817 } 818 c -= command.width; 819 } 820 } 821 buf.append("}"); 822 return buf.toString(); 823 } 824 825 public final Object getProperty(String name, Object defaultValue) { 826 Object o = defaultValue; 827 int maxSolve = Integer.MIN_VALUE; 828 int i = -1; 829 for (Member member : getNonAllMembers()) { 830 i++; 831 // more than one usage 832 if (member == null) { 833 if (getLogger().isDebugEnabled()) { 834 getLogger().debug( 835 "RolapEvaluator.getProperty: member == null " 836 + " , count=" + i); 837 } 838 continue; 839 } 840 841 // Don't call member.getPropertyValue unless this member's 842 // solve order is greater than one we've already seen. 843 // The getSolveOrder call is cheap call compared to the 844 // getPropertyValue call, and when we're evaluating millions 845 // of members, this has proven to make a significant performance 846 // difference. 847 final int solve = member.getSolveOrder(); 848 if (solve > maxSolve) { 849 final Object p = member.getPropertyValue(name); 850 if (p != null) { 851 o = p; 852 maxSolve = solve; 853 } 854 } 855 } 856 return o; 857 } 858 859 /** 860 * Returns the format string for this cell. This is computed by evaluating 861 * the format expression in the current context, and therefore different 862 * cells may have different format strings. 863 * 864 * @post return != null 865 */ 866 public final String getFormatString() { 867 final Exp formatExp = 868 (Exp) getProperty(Property.FORMAT_EXP_PARSED.name, null); 869 if (formatExp == null) { 870 return "Standard"; 871 } 872 final Calc formatCalc = root.getCompiled(formatExp, true, null); 873 final Object o = formatCalc.evaluate(this); 874 if (o == null) { 875 return "Standard"; 876 } 877 return o.toString(); 878 } 879 880 private Format getFormat() { 881 final String formatString = getFormatString(); 882 return getFormat(formatString); 883 } 884 885 private Format getFormat(String formatString) { 886 return Format.get(formatString, root.connection.getLocale()); 887 } 888 889 public final Locale getConnectionLocale() { 890 return root.connection.getLocale(); 891 } 892 893 public final String format(Object o) { 894 if (o == Util.nullValue) { 895 o = null; 896 } 897 if (o instanceof Throwable) { 898 return "#ERR: " + o.toString(); 899 } 900 Format format = getFormat(); 901 return format.format(o); 902 } 903 904 public final String format(Object o, String formatString) { 905 if (o == Util.nullValue) { 906 o = null; 907 } 908 if (o instanceof Throwable) { 909 return "#ERR: " + o.toString(); 910 } 911 Format format = getFormat(formatString); 912 return format.format(o); 913 } 914 915 /** 916 * Creates a key which uniquely identifes an expression and its 917 * context. The context includes members of dimensions which the 918 * expression is dependent upon. 919 */ 920 private Object getExpResultCacheKey(ExpCacheDescriptor descriptor) { 921 // in NON EMPTY mode the result depends on everything, e.g. 922 // "NON EMPTY [Customer].[Name].members" may return different results 923 // for 1997-01 and 1997-02 924 final List<Object> key; 925 if (nonEmpty) { 926 key = new ArrayList<Object>(currentMembers.length + 1); 927 key.add(descriptor.getExp()); 928 //noinspection ManualArrayToCollectionCopy 929 for (RolapMember currentMember : currentMembers) { 930 key.add(currentMember); 931 } 932 } else { 933 final int[] hierarchyOrdinals = 934 descriptor.getDependentHierarchyOrdinals(); 935 key = new ArrayList<Object>(hierarchyOrdinals.length + 1); 936 key.add(descriptor.getExp()); 937 for (final int hierarchyOrdinal : hierarchyOrdinals) { 938 final Member member = currentMembers[hierarchyOrdinal]; 939 assert member != null; 940 key.add(member); 941 } 942 } 943 return key; 944 } 945 946 public final Object getCachedResult(ExpCacheDescriptor cacheDescriptor) { 947 // Look up a cached result, and if not present, compute one and add to 948 // cache. Use a dummy value to represent nulls. 949 final Object key = getExpResultCacheKey(cacheDescriptor); 950 Object result = root.getCacheResult(key); 951 if (result == null) { 952 boolean aggCacheDirty = cellReader.isDirty(); 953 int aggregateCacheMissCountBefore = cellReader.getMissCount(); 954 result = cacheDescriptor.evaluate(this); 955 int aggregateCacheMissCountAfter = cellReader.getMissCount(); 956 957 boolean isValidResult; 958 959 if (!aggCacheDirty 960 && (aggregateCacheMissCountBefore 961 == aggregateCacheMissCountAfter)) 962 { 963 // Cache the evaluation result as valid result if the 964 // evaluation did not use any missing aggregates. Missing 965 // aggregates could be used when aggregate cache is not fully 966 // loaded, or if new missing aggregates are seen. 967 isValidResult = true; 968 } else { 969 // Cache the evaluation result as invalid result if the 970 // evaluation uses missing aggregates. 971 isValidResult = false; 972 } 973 root.putCacheResult( 974 key, 975 result == null ? nullResult : result, 976 isValidResult); 977 } else if (result == nullResult) { 978 result = null; 979 } 980 981 return result; 982 } 983 984 public final void clearExpResultCache(boolean clearValidResult) { 985 root.clearResultCache(clearValidResult); 986 } 987 988 public final boolean isNonEmpty() { 989 return nonEmpty; 990 } 991 992 public final void setNonEmpty(boolean nonEmpty) { 993 if (nonEmpty != this.nonEmpty) { 994 ensureCommandCapacity(commandCount + 2); 995 commands[commandCount++] = this.nonEmpty; 996 commands[commandCount++] = Command.SET_NON_EMPTY; 997 this.nonEmpty = nonEmpty; 998 } 999 } 1000 1001 public final RuntimeException newEvalException(Object context, String s) { 1002 return FunUtil.newEvalException((FunDef) context, s); 1003 } 1004 1005 public final NamedSetEvaluator getNamedSetEvaluator( 1006 NamedSet namedSet, 1007 boolean create) 1008 { 1009 return root.evaluateNamedSet(namedSet, create); 1010 } 1011 1012 public final SetEvaluator getSetEvaluator( 1013 Exp exp, 1014 boolean create) 1015 { 1016 return root.evaluateSet(exp, create); 1017 } 1018 1019 public final int getMissCount() { 1020 return cellReader.getMissCount(); 1021 } 1022 1023 public final Object getParameterValue(ParameterSlot slot) { 1024 return root.getParameterValue(slot); 1025 } 1026 1027 final void addCalculation( 1028 RolapCalculation calculation, 1029 boolean reversible) 1030 { 1031 assert calculation != null; 1032 calculations[calculationCount++] = calculation; 1033 1034 if (reversible && !(calculation instanceof RolapMember)) { 1035 // Add command to remove this calculation. 1036 ensureCommandCapacity(commandCount + 2); 1037 commands[commandCount++] = calculation; 1038 commands[commandCount++] = Command.REMOVE_CALCULATION; 1039 } 1040 } 1041 1042 /** 1043 * Returns the member with the highest solve order according to AS2000 1044 * rules. This was the behavior prior to solve order mode being 1045 * configurable. 1046 * 1047 * <p>The SOLVE_ORDER value is absolute regardless of where it is defined; 1048 * e.g. a query defined calculated member with a SOLVE_ORDER of 1 always 1049 * takes precedence over a cube defined value of 2. 1050 * 1051 * <p>No special consideration is given to the aggregate function. 1052 */ 1053 private RolapCalculation getAbsoluteMaxSolveOrder() { 1054 // Find member with the highest solve order. 1055 RolapCalculation maxSolveMember = calculations[0]; 1056 for (int i = 1; i < calculationCount; i++) { 1057 RolapCalculation member = calculations[i]; 1058 if (expandsBefore(member, maxSolveMember)) { 1059 maxSolveMember = member; 1060 } 1061 } 1062 return maxSolveMember; 1063 } 1064 1065 /** 1066 * Returns the member with the highest solve order according to AS2005 1067 * scoping rules. 1068 * 1069 * <p>By default, cube calculated members are resolved before any session 1070 * scope calculated members, and session scope members are resolved before 1071 * any query defined calculation. The SOLVE_ORDER value only applies within 1072 * the scope in which it was defined. 1073 * 1074 * <p>The aggregate function is always applied to base members; i.e. as if 1075 * SOLVE_ORDER was defined to be the lowest value in a given evaluation in a 1076 * SSAS2000 sense. 1077 */ 1078 private RolapCalculation getScopedMaxSolveOrder() { 1079 // Finite state machine that determines the member with the highest 1080 // solve order. 1081 RolapCalculation maxSolveMember = null; 1082 ScopedMaxSolveOrderFinderState state = 1083 ScopedMaxSolveOrderFinderState.START; 1084 for (int i = 0; i < calculationCount; i++) { 1085 RolapCalculation calculation = calculations[i]; 1086 switch (state) { 1087 case START: 1088 maxSolveMember = calculation; 1089 if (maxSolveMember.containsAggregateFunction()) { 1090 state = ScopedMaxSolveOrderFinderState.AGG_SCOPE; 1091 } else if (maxSolveMember.isCalculatedInQuery()) { 1092 state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; 1093 } else { 1094 state = ScopedMaxSolveOrderFinderState.CUBE_SCOPE; 1095 } 1096 break; 1097 1098 case AGG_SCOPE: 1099 if (calculation.containsAggregateFunction()) { 1100 if (expandsBefore(calculation, maxSolveMember)) { 1101 maxSolveMember = calculation; 1102 } 1103 } else if (calculation.isCalculatedInQuery()) { 1104 maxSolveMember = calculation; 1105 state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; 1106 } else { 1107 maxSolveMember = calculation; 1108 state = ScopedMaxSolveOrderFinderState.CUBE_SCOPE; 1109 } 1110 break; 1111 1112 case CUBE_SCOPE: 1113 if (calculation.containsAggregateFunction()) { 1114 continue; 1115 } 1116 1117 if (calculation.isCalculatedInQuery()) { 1118 maxSolveMember = calculation; 1119 state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; 1120 } else if (expandsBefore(calculation, maxSolveMember)) { 1121 maxSolveMember = calculation; 1122 } 1123 break; 1124 1125 case QUERY_SCOPE: 1126 if (calculation.containsAggregateFunction()) { 1127 continue; 1128 } 1129 1130 if (calculation.isCalculatedInQuery()) { 1131 if (expandsBefore(calculation, maxSolveMember)) { 1132 maxSolveMember = calculation; 1133 } 1134 } 1135 break; 1136 } 1137 } 1138 1139 return maxSolveMember; 1140 } 1141 1142 /** 1143 * Returns whether a given calculation expands before another. 1144 * A calculation expands before another if its solve order is higher, 1145 * or if its solve order is the same and its dimension ordinal is lower. 1146 * 1147 * @param calc1 First calculated member or tuple 1148 * @param calc2 Second calculated member or tuple 1149 * @return Whether calc1 expands before calc2 1150 */ 1151 private boolean expandsBefore( 1152 RolapCalculation calc1, 1153 RolapCalculation calc2) 1154 { 1155 final int solveOrder1 = calc1.getSolveOrder(); 1156 final int solveOrder2 = calc2.getSolveOrder(); 1157 if (solveOrder1 > solveOrder2) { 1158 return true; 1159 } else { 1160 return solveOrder1 == solveOrder2 1161 && calc1.getHierarchyOrdinal() 1162 < calc2.getHierarchyOrdinal(); 1163 } 1164 } 1165 1166 final void removeCalculation( 1167 RolapCalculation calculation, 1168 boolean reversible) 1169 { 1170 for (int i = 0; i < calculationCount; i++) { 1171 if (calculations[i] == calculation) { 1172 // overwrite this member with the end member 1173 --calculationCount; 1174 calculations[i] = calculations[calculationCount]; 1175 assert calculations[i] != null; 1176 calculations[calculationCount] = null; // to allow gc 1177 1178 if (reversible && !(calculation instanceof RolapMember)) { 1179 // Add a command to re-add the calculation. 1180 ensureCommandCapacity(commandCount + 2); 1181 commands[commandCount++] = calculation; 1182 commands[commandCount++] = Command.ADD_CALCULATION; 1183 } 1184 return; 1185 } 1186 } 1187 throw new AssertionError( 1188 "calculation " + calculation + " not on stack"); 1189 } 1190 1191 public final int getIterationLength() { 1192 return iterationLength; 1193 } 1194 1195 public final void setIterationLength(int iterationLength) { 1196 ensureCommandCapacity(commandCount + 2); 1197 commands[commandCount++] = this.iterationLength; 1198 commands[commandCount++] = Command.SET_ITERATION_LENGTH; 1199 this.iterationLength = iterationLength; 1200 } 1201 1202 public final boolean isEvalAxes() { 1203 return evalAxes; 1204 } 1205 1206 public final void setEvalAxes(boolean evalAxes) { 1207 if (evalAxes != this.evalAxes) { 1208 ensureCommandCapacity(commandCount + 2); 1209 commands[commandCount++] = this.evalAxes; 1210 commands[commandCount++] = Command.SET_EVAL_AXES; 1211 this.evalAxes = evalAxes; 1212 } 1213 } 1214 1215 private int checksumState() { 1216 int h = 0; 1217 h = h * 31 + Arrays.asList(currentMembers).hashCode(); 1218 h = h * 31 + new HashSet<RolapCalculation>( 1219 Arrays.asList(calculations) 1220 .subList(0, calculationCount)).hashCode(); 1221 h = h * 31 + slicerMembers.hashCode(); 1222 h = h * 31 + (expandingMember == null ? 0 : expandingMember.hashCode()); 1223 h = h * 31 1224 + (aggregationLists == null ? 0 : aggregationLists.hashCode()); 1225 h = h * 31 1226 + (nonEmpty ? 0x1 : 0x2) 1227 + (nativeEnabled ? 0x4 : 0x8) 1228 + (firstExpanding ? 0x10 : 0x20) 1229 + (evalAxes ? 0x40 : 0x80); 1230 if (false) { 1231 // Enable this code block to debug checksum mismatches. 1232 System.err.println( 1233 "h=" + h + ": " + Arrays.asList( 1234 Arrays.asList(currentMembers), 1235 new HashSet<RolapCalculation>( 1236 Arrays.asList(calculations).subList( 1237 0, calculationCount)), 1238 expandingMember, 1239 aggregationLists, 1240 nonEmpty, 1241 nativeEnabled, 1242 firstExpanding, 1243 evalAxes)); 1244 } 1245 return h; 1246 } 1247 1248 /** 1249 * Checks if unrelated dimensions to the measure in the current context 1250 * should be ignored. 1251 * @return boolean 1252 */ 1253 public boolean shouldIgnoreUnrelatedDimensions() { 1254 return getCube().shouldIgnoreUnrelatedDimensions( 1255 getMeasureCube().getName()); 1256 } 1257 1258 private enum Command { 1259 SET_CONTEXT(2) { 1260 @Override 1261 void execute(RolapEvaluator evaluator) { 1262 final int memberOrdinal = 1263 (Integer) evaluator.commands[--evaluator.commandCount]; 1264 final RolapMember member = 1265 (RolapMember) evaluator.commands[--evaluator.commandCount]; 1266 evaluator.setContext(member, false); 1267 } 1268 }, 1269 SET_NATIVE_ENABLED(1) { 1270 @Override 1271 void execute(RolapEvaluator evaluator) { 1272 evaluator.nativeEnabled = 1273 (Boolean) evaluator.commands[--evaluator.commandCount]; 1274 } 1275 }, 1276 SET_NON_EMPTY(1) { 1277 @Override 1278 void execute(RolapEvaluator evaluator) { 1279 evaluator.nonEmpty = 1280 (Boolean) evaluator.commands[--evaluator.commandCount]; 1281 } 1282 }, 1283 SET_EVAL_AXES(1) { 1284 @Override 1285 void execute(RolapEvaluator evaluator) { 1286 evaluator.evalAxes = 1287 (Boolean) evaluator.commands[--evaluator.commandCount]; 1288 } 1289 }, 1290 SET_EXPANDING(2) { 1291 @Override 1292 void execute(RolapEvaluator evaluator) { 1293 evaluator.firstExpanding = 1294 (Boolean) evaluator.commands[--evaluator.commandCount]; 1295 evaluator.expandingMember = 1296 (Member) evaluator.commands[--evaluator.commandCount]; 1297 } 1298 }, 1299 SET_ITERATION_LENGTH(1) { 1300 @Override 1301 void execute(RolapEvaluator evaluator) { 1302 evaluator.iterationLength = 1303 (Integer) evaluator.commands[--evaluator.commandCount]; 1304 } 1305 }, 1306 SET_CELL_READER(1) { 1307 @Override 1308 void execute(RolapEvaluator evaluator) { 1309 evaluator.cellReader = 1310 (CellReader) evaluator.commands[--evaluator.commandCount]; 1311 } 1312 }, 1313 CHECKSUM(1) { 1314 @Override 1315 void execute(RolapEvaluator evaluator) { 1316 final int value = 1317 (Integer) evaluator.commands[--evaluator.commandCount]; 1318 final int currentState = evaluator.checksumState(); 1319 assert value == currentState 1320 : "Current checksum " + currentState 1321 + " != previous checksum " + value; 1322 } 1323 }, 1324 ADD_CALCULATION(1) { 1325 @Override 1326 void execute(RolapEvaluator evaluator) { 1327 final RolapCalculation calculation = 1328 (RolapCalculation) 1329 evaluator.commands[--evaluator.commandCount]; 1330 evaluator.calculations[evaluator.calculationCount++] = 1331 calculation; 1332 } 1333 }, 1334 REMOVE_CALCULATION(1) { 1335 @Override 1336 void execute(RolapEvaluator evaluator) { 1337 final RolapCalculation calculation = 1338 (RolapCalculation) 1339 evaluator.commands[--evaluator.commandCount]; 1340 evaluator.removeCalculation(calculation, false); 1341 } 1342 }, 1343 SAVEPOINT(0) { 1344 @Override 1345 void execute(RolapEvaluator evaluator) { 1346 // nothing to do; command is just a marker 1347 } 1348 }; 1349 1350 public final int width; 1351 1352 Command(int argCount) { 1353 this.width = argCount + 1; 1354 } 1355 1356 abstract void execute(RolapEvaluator evaluator); 1357 } 1358} 1359 1360// End RolapEvaluator.java