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