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