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) 2005-2005 Julian Hyde
008// Copyright (C) 2005-2013 Pentaho
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.*;
015import mondrian.mdx.ResolvedFunCall;
016import mondrian.olap.*;
017import mondrian.olap.type.TupleType;
018import mondrian.olap.type.Type;
019import mondrian.rolap.RolapUtil;
020
021import java.io.PrintWriter;
022import java.util.*;
023
024/**
025 * Definition of the <code>RANK</code> MDX function.
026 *
027 * @author Richard Emberson
028 * @since 17 January, 2005
029 */
030public class RankFunDef extends FunDefBase {
031    static final boolean debug = false;
032    static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver(
033        "Rank",
034            "Rank(<Tuple>, <Set> [, <Calc Expression>])",
035            "Returns the one-based rank of a tuple in a set.",
036            new String[]{"fitx", "fitxn", "fimx", "fimxn"},
037            RankFunDef.class);
038
039    public RankFunDef(FunDef dummyFunDef) {
040        super(dummyFunDef);
041    }
042
043    public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
044        switch (call.getArgCount()) {
045        case 2:
046            return compileCall2(call, compiler);
047        case 3:
048            return compileCall3(call, compiler);
049        default:
050            throw Util.newInternal("invalid arg count " + call.getArgCount());
051        }
052    }
053
054    public Calc compileCall3(ResolvedFunCall call, ExpCompiler compiler) {
055        final Type type0 = call.getArg(0).getType();
056        final ListCalc listCalc =
057            compiler.compileList(call.getArg(1));
058        final Calc keyCalc =
059            compiler.compileScalar(call.getArg(2), true);
060        Calc sortedListCalc =
061            new SortedListCalc(call, listCalc, keyCalc);
062        final ExpCacheDescriptor cacheDescriptor =
063            new ExpCacheDescriptor(
064                call, sortedListCalc, compiler.getEvaluator());
065        if (type0 instanceof TupleType) {
066            final TupleCalc tupleCalc =
067                compiler.compileTuple(call.getArg(0));
068            return new Rank3TupleCalc(
069                call, tupleCalc, keyCalc, cacheDescriptor);
070        } else {
071            final MemberCalc memberCalc =
072                compiler.compileMember(call.getArg(0));
073            return new Rank3MemberCalc(
074                call, memberCalc, keyCalc, cacheDescriptor);
075        }
076    }
077
078    public Calc compileCall2(ResolvedFunCall call, ExpCompiler compiler) {
079        final boolean tuple = call.getArg(0).getType() instanceof TupleType;
080        final Exp listExp = call.getArg(1);
081        final ListCalc listCalc0 = compiler.compileList(listExp);
082        Calc listCalc1 = new RankedListCalc(listCalc0, tuple);
083        final Calc listCalc;
084        if (MondrianProperties.instance().EnableExpCache.get()) {
085            final ExpCacheDescriptor key = new ExpCacheDescriptor(
086                listExp, listCalc1, compiler.getEvaluator());
087            listCalc = new CacheCalc(listExp, key);
088        } else {
089            listCalc = listCalc1;
090        }
091        if (tuple) {
092            final TupleCalc tupleCalc =
093                    compiler.compileTuple(call.getArg(0));
094            return new Rank2TupleCalc(call, tupleCalc, listCalc);
095        } else {
096            final MemberCalc memberCalc =
097                    compiler.compileMember(call.getArg(0));
098            return new Rank2MemberCalc(call, memberCalc, listCalc);
099        }
100    }
101
102    private static class Rank2TupleCalc extends AbstractIntegerCalc {
103        private final TupleCalc tupleCalc;
104        private final Calc listCalc;
105
106        public Rank2TupleCalc(
107            ResolvedFunCall call, TupleCalc tupleCalc, Calc listCalc)
108        {
109            super(call, new Calc[] {tupleCalc, listCalc});
110            this.tupleCalc = tupleCalc;
111            this.listCalc = listCalc;
112        }
113
114        public int evaluateInteger(Evaluator evaluator) {
115            // Get member or tuple.
116            // If the member is null (or the tuple contains a null member)
117            // the result is null (even if the list is null).
118            final Member[] members = tupleCalc.evaluateTuple(evaluator);
119            if (members == null) {
120                return IntegerNull;
121            }
122            assert !tupleContainsNullMember(members);
123
124            // Get the set of members/tuples.
125            // If the list is empty, MSAS cannot figure out the type of the
126            // list, so returns an error "Formula error - dimension count is
127            // not valid - in the Rank function". We will naturally return 0,
128            // which I think is better.
129            final RankedTupleList rankedTupleList =
130                (RankedTupleList) listCalc.evaluate(evaluator);
131            if (rankedTupleList == null) {
132                return 0;
133            }
134
135            // Find position of member in list. -1 signifies not found.
136            final List<Member> memberList = Arrays.asList(members);
137            final int i = rankedTupleList.indexOf(memberList);
138            // Return 1-based rank. 0 signifies not found.
139            return i + 1;
140        }
141    }
142
143    private static class Rank2MemberCalc extends AbstractIntegerCalc {
144        private final MemberCalc memberCalc;
145        private final Calc listCalc;
146
147        public Rank2MemberCalc(
148            ResolvedFunCall call, MemberCalc memberCalc, Calc listCalc)
149        {
150            super(call, new Calc[] {memberCalc, listCalc});
151            this.memberCalc = memberCalc;
152            this.listCalc = listCalc;
153        }
154
155        public int evaluateInteger(Evaluator evaluator) {
156            // Get member or tuple.
157            // If the member is null (or the tuple contains a null member)
158            // the result is null (even if the list is null).
159            final Member member = memberCalc.evaluateMember(evaluator);
160            if (member == null
161                || member.isNull())
162            {
163                return IntegerNull;
164            }
165            // Get the set of members/tuples.
166            // If the list is empty, MSAS cannot figure out the type of the
167            // list, so returns an error "Formula error - dimension count is
168            // not valid - in the Rank function". We will naturally return 0,
169            // which I think is better.
170            RankedMemberList rankedMemberList =
171                (RankedMemberList) listCalc.evaluate(evaluator);
172            if (rankedMemberList == null) {
173                return 0;
174            }
175
176            // Find position of member in list. -1 signifies not found.
177            final int i = rankedMemberList.indexOf(member);
178            // Return 1-based rank. 0 signifies not found.
179            return i + 1;
180        }
181    }
182
183    private static class Rank3TupleCalc extends AbstractIntegerCalc {
184        private final TupleCalc tupleCalc;
185        private final Calc sortCalc;
186        private final ExpCacheDescriptor cacheDescriptor;
187
188        public Rank3TupleCalc(
189            ResolvedFunCall call,
190            TupleCalc tupleCalc,
191            Calc sortCalc,
192            ExpCacheDescriptor cacheDescriptor)
193        {
194            super(call, new Calc[] {tupleCalc, sortCalc});
195            this.tupleCalc = tupleCalc;
196            this.sortCalc = sortCalc;
197            this.cacheDescriptor = cacheDescriptor;
198        }
199
200        public int evaluateInteger(Evaluator evaluator) {
201            Member[] members = tupleCalc.evaluateTuple(evaluator);
202            if (members == null) {
203                return IntegerNull;
204            }
205            assert !tupleContainsNullMember(members);
206
207            // Evaluate the list (or retrieve from cache).
208            // If there is an exception while calculating the
209            // list, propagate it up.
210            final TupleSortResult sortResult =
211                (TupleSortResult) evaluator.getCachedResult(cacheDescriptor);
212            if (debug) {
213                sortResult.print(new PrintWriter(System.out));
214            }
215
216            if (sortResult.isEmpty()) {
217                // If list is empty, the rank is null.
218                return IntegerNull;
219            }
220
221            // First try to find the member in the cached SortResult
222            Integer rank = sortResult.rankOf(members);
223            if (rank != null) {
224                return rank;
225            }
226
227            // member is not seen before, now compute the value of the tuple.
228            final int savepoint = evaluator.savepoint();
229            Object value;
230            try {
231                evaluator.setContext(members);
232                value = sortCalc.evaluate(evaluator);
233            } finally {
234                evaluator.restore(savepoint);
235            }
236
237            if (valueNotReady(value)) {
238                // The value wasn't ready, so quit now... we'll be back.
239                return 0;
240            }
241
242            // If value is null, it won't be in the values array.
243            if (value == Util.nullValue || value == null) {
244                return sortResult.values.length + 1;
245            }
246
247            value = coerceValue(sortResult.values, value);
248
249            // Look for the ranked value in the array.
250            int j = Arrays.binarySearch(
251                sortResult.values, value, Collections.<Object>reverseOrder());
252            if (j < 0) {
253                // Value not found. Flip the result to find the
254                // insertion point.
255                j = -(j + 1);
256            }
257            return j + 1; // 1-based
258        }
259    }
260
261    private static class Rank3MemberCalc extends AbstractIntegerCalc {
262        private final MemberCalc memberCalc;
263        private final Calc sortCalc;
264        private final ExpCacheDescriptor cacheDescriptor;
265
266        public Rank3MemberCalc(
267            ResolvedFunCall call,
268            MemberCalc memberCalc,
269            Calc sortCalc,
270            ExpCacheDescriptor cacheDescriptor)
271        {
272            super(call, new Calc[] {memberCalc, sortCalc});
273            this.memberCalc = memberCalc;
274            this.sortCalc = sortCalc;
275            this.cacheDescriptor = cacheDescriptor;
276        }
277
278        public int evaluateInteger(Evaluator evaluator) {
279            Member member = memberCalc.evaluateMember(evaluator);
280            if (member == null || member.isNull()) {
281                return IntegerNull;
282            }
283
284            // Evaluate the list (or retrieve from cache).
285            // If there was an exception while calculating the
286            // list, propagate it up.
287            final MemberSortResult sortResult =
288                (MemberSortResult) evaluator.getCachedResult(cacheDescriptor);
289            if (debug) {
290                sortResult.print(new PrintWriter(System.out));
291            }
292            if (sortResult.isEmpty()) {
293                // If list is empty, the rank is null.
294                return IntegerNull;
295            }
296
297            // First try to find the member in the cached SortResult
298            Integer rank = sortResult.rankOf(member);
299            if (rank != null) {
300                return rank;
301            }
302
303            // member is not seen before, now compute the value of the tuple.
304            final int savepoint = evaluator.savepoint();
305            evaluator.setContext(member);
306            Object value;
307            try {
308                value = sortCalc.evaluate(evaluator);
309            } finally {
310                evaluator.restore(savepoint);
311            }
312
313            if (valueNotReady(value)) {
314                // The value wasn't ready, so quit now... we'll be back.
315                return 0;
316            }
317
318            // If value is null, it won't be in the values array.
319            if (value == Util.nullValue || value == null) {
320                return sortResult.values.length + 1;
321            }
322
323            value = coerceValue(sortResult.values, value);
324
325            // Look for the ranked value in the array.
326            int j = Arrays.binarySearch(
327                sortResult.values, value, Collections.<Object>reverseOrder());
328            if (j < 0) {
329                // Value not found. Flip the result to find the
330                // insertion point.
331                j = -(j + 1);
332            }
333            return j + 1; // 1-based
334        }
335    }
336
337    private static Object coerceValue(Object[] values, Object value) {
338        if (values.length > 0) {
339            final Object firstValue = values[0];
340            if (firstValue instanceof Integer && value instanceof Double) {
341                return  ((Double) value).intValue();
342            }
343        }
344        return value;
345    }
346
347    private static boolean valueNotReady(Object value) {
348        return value == RolapUtil.valueNotReadyException
349            || value == new Double(Double.NaN);
350    }
351
352    /**
353     * Calc which evaluates an expression to form a list of tuples,
354     * evaluates a scalar expression at each tuple, then sorts the list of
355     * values. The result is a value of type {@link SortResult}, and can be
356     * used to implement the <code>Rank</code> function efficiently.
357     */
358    private static class SortedListCalc extends AbstractCalc {
359        private final ListCalc listCalc;
360        private final Calc keyCalc;
361
362        private static final Integer ONE = 1;
363
364        /**
365         * Creates a SortCalc.
366         *
367         * @param exp Source expression
368         * @param listCalc Compiled expression to compute the list
369         * @param keyCalc Compiled expression to compute the sort key
370         */
371        public SortedListCalc(
372            Exp exp,
373            ListCalc listCalc,
374            Calc keyCalc)
375        {
376            super(exp, new Calc[] {listCalc, keyCalc});
377            this.listCalc = listCalc;
378            this.keyCalc = keyCalc;
379        }
380
381        public boolean dependsOn(Hierarchy hierarchy) {
382            return anyDependsButFirst(getCalcs(), hierarchy);
383        }
384
385        public Object evaluate(Evaluator evaluator) {
386            // Save the state of the evaluator.
387            final int savepoint = evaluator.savepoint();
388            RuntimeException exception = null;
389            final Map<Member, Object> memberValueMap;
390            final Map<List<Member>, Object> tupleValueMap;
391            final int numValues;
392            //noinspection unchecked
393            final Map<Object, Integer> uniqueValueCounterMap =
394                new TreeMap<Object, Integer>(
395                    FunUtil.DescendingValueComparator.instance);
396            TupleList list;
397            try {
398                evaluator.setNonEmpty(false);
399
400                // Construct an array containing the value of the expression
401                // for each member.
402
403                list = listCalc.evaluateList(evaluator);
404                assert list != null;
405                if (list.isEmpty()) {
406                    return list.getArity() == 1
407                        ? new MemberSortResult(
408                            new Object[0],
409                            Collections.<Member, Integer>emptyMap())
410                    : new TupleSortResult(
411                        new Object[0],
412                        Collections.<List<Member>, Integer>emptyMap());
413                }
414
415                if (list.getArity() == 1) {
416                    memberValueMap = new HashMap<Member, Object>();
417                    tupleValueMap = null;
418                    for (Member member : list.slice(0)) {
419                        evaluator.setContext(member);
420                        final Object keyValue = keyCalc.evaluate(evaluator);
421                        if (keyValue instanceof RuntimeException) {
422                            if (exception == null) {
423                                exception = (RuntimeException) keyValue;
424                            }
425                        } else if (Util.isNull(keyValue)) {
426                            // nothing to do
427                        } else {
428                            // Assume it's the first time seeing this keyValue.
429                            Integer valueCounter =
430                                uniqueValueCounterMap.put(keyValue, ONE);
431                            if (valueCounter != null) {
432                                // Update the counter on how many times this
433                                // keyValue has been seen.
434                                uniqueValueCounterMap.put(
435                                    keyValue, valueCounter + 1);
436                            }
437                            memberValueMap.put(member, keyValue);
438                        }
439                    }
440                    numValues = memberValueMap.keySet().size();
441                } else {
442                    tupleValueMap = new HashMap<List<Member>, Object>();
443                    memberValueMap = null;
444                    for (List<Member> tuple : list) {
445                        evaluator.setContext(tuple);
446                        final Object keyValue = keyCalc.evaluate(evaluator);
447                        if (keyValue instanceof RuntimeException) {
448                            if (exception == null) {
449                                exception = (RuntimeException) keyValue;
450                            }
451                        } else if (Util.isNull(keyValue)) {
452                            // nothing to do
453                        } else {
454                            // Assume it's the first time seeing this keyValue.
455                            Integer valueCounter = uniqueValueCounterMap.put(
456                                keyValue, ONE);
457                            if (valueCounter != null) {
458                                // Update the counter on how many times this
459                                // keyValue has been seen.
460                                uniqueValueCounterMap.put(
461                                    keyValue, valueCounter + 1);
462                            }
463                            tupleValueMap.put(tuple, keyValue);
464                        }
465                    }
466                    numValues = tupleValueMap.keySet().size();
467                }
468            } finally {
469                evaluator.restore(savepoint);
470            }
471
472
473            // If there were exceptions, quit now... we'll be back.
474            if (exception != null) {
475                return exception;
476            }
477
478            final Object[] allValuesSorted = new Object[numValues];
479
480            // Now build the sorted array containing all keyValues
481            // And update the counter to the rank
482            int currentOrdinal = 0;
483            //noinspection unchecked
484            final Map<Object, Integer> uniqueValueRankMap =
485                new TreeMap<Object, Integer>(
486                    Collections.<Object>reverseOrder());
487
488            for (Map.Entry<Object, Integer> entry
489                : uniqueValueCounterMap.entrySet())
490            {
491                Object keyValue = entry.getKey();
492                Integer valueCount = entry.getValue();
493                // Because uniqueValueCounterMap is already sorted, so the
494                // reconstructed allValuesSorted is guaranteed to be sorted.
495                for (int i = 0; i < valueCount; i ++) {
496                    allValuesSorted[currentOrdinal + i] = keyValue;
497                }
498                uniqueValueRankMap.put(keyValue, currentOrdinal + 1);
499                currentOrdinal += valueCount;
500            }
501
502            // Build a member/tuple to rank map
503            if (list.getArity() == 1) {
504                final Map<Member, Integer> rankMap =
505                    new HashMap<Member, Integer>();
506                for (Map.Entry<Member, Object> entry
507                    : memberValueMap.entrySet())
508                {
509                    int oneBasedRank =
510                        uniqueValueRankMap.get(entry.getValue());
511                    rankMap.put(entry.getKey(), oneBasedRank);
512                }
513                return new MemberSortResult(allValuesSorted, rankMap);
514            } else {
515                final Map<List<Member>, Integer> rankMap =
516                    new HashMap<List<Member>, Integer>();
517                for (Map.Entry<List<Member>, Object> entry
518                    : tupleValueMap.entrySet())
519                {
520                    int oneBasedRank =
521                        uniqueValueRankMap.get(entry.getValue());
522                    rankMap.put(entry.getKey(), oneBasedRank);
523                }
524                return new TupleSortResult(allValuesSorted, rankMap);
525            }
526        }
527    }
528
529    /**
530     * Holder for the result of sorting a set of values.
531     * It provides simple interface to look up the rank for a member or a tuple.
532     */
533    private static abstract class SortResult {
534        /**
535         * All values in sorted order; Duplicates are not removed.
536         * E.g. Set (15,15,5,0)
537         *  10 should be ranked 3.
538         *
539         * <p>Null values are not present: they would be at the end, anyway.
540         */
541        final Object[] values;
542
543
544        public SortResult(Object[] values) {
545            this.values = values;
546        }
547
548        public boolean isEmpty() {
549            return values == null;
550        }
551
552        public void print(PrintWriter pw) {
553            if (values == null) {
554                pw.println("SortResult: empty");
555            } else {
556                pw.println("SortResult {");
557                for (int i = 0; i < values.length; i++) {
558                    if (i > 0) {
559                        pw.println(",");
560                    }
561                    Object value = values[i];
562                    pw.print(value);
563                }
564                pw.println("}");
565            }
566            pw.flush();
567        }
568    }
569
570    private static class MemberSortResult extends SortResult {
571        /**
572         * The precomputed rank associated with all members
573         */
574        final Map<Member, Integer> rankMap;
575
576        public MemberSortResult(
577            Object[] values, Map<Member, Integer> rankMap)
578        {
579            super(values);
580            this.rankMap = rankMap;
581        }
582
583        public Integer rankOf(Member member) {
584            return rankMap.get(member);
585        }
586    }
587
588    private static class TupleSortResult extends SortResult {
589        /**
590         * The precomputed rank associated with all tuples
591         */
592        final Map<List<Member>, Integer> rankMap;
593
594        public TupleSortResult(
595            Object[] values, Map<List<Member>, Integer> rankMap)
596        {
597            super(values);
598            this.rankMap = rankMap;
599        }
600
601        public Integer rankOf(Member[] tuple) {
602            return rankMap.get(Arrays.asList(tuple));
603        }
604    }
605
606    /**
607     * Expression which evaluates an expression to form a list of tuples.
608     *
609     * <p>The result is a value of type
610     * {@link mondrian.olap.fun.RankFunDef.RankedMemberList} or
611     * {@link mondrian.olap.fun.RankFunDef.RankedTupleList}, or
612     * null if the list is empty.
613     */
614    private static class RankedListCalc extends AbstractCalc {
615        private final ListCalc listCalc;
616        private final boolean tuple;
617
618        /**
619         * Creates a RankedListCalc.
620         *
621         * @param listCalc Compiled expression to compute the list
622         * @param tuple Whether elements of the list are tuples (as opposed to
623         * members)
624         */
625        public RankedListCalc(ListCalc listCalc, boolean tuple) {
626            super(new DummyExp(listCalc.getType()), new Calc[] {listCalc});
627            this.listCalc = listCalc;
628            this.tuple = tuple;
629        }
630
631        public Object evaluate(Evaluator evaluator) {
632            // Construct an array containing the value of the expression
633            // for each member.
634            TupleList tupleList = listCalc.evaluateList(evaluator);
635            assert tupleList != null;
636            if (tuple) {
637                return new RankedTupleList(tupleList);
638            } else {
639                return new RankedMemberList(tupleList.slice(0));
640            }
641        }
642    }
643
644    /**
645     * Data structure which contains a list and can return the position of an
646     * element in the list in O(log N).
647     */
648    static class RankedMemberList {
649        Map<Member, Integer> map = new HashMap<Member, Integer>();
650
651        RankedMemberList(List<Member> members) {
652            int i = -1;
653            for (final Member member : members) {
654                ++i;
655                final Integer value = map.put(member, i);
656                if (value != null) {
657                    // The list already contained a value for this key -- put
658                    // it back.
659                    map.put(member, value);
660                }
661            }
662        }
663
664        int indexOf(Member m) {
665            Integer integer = map.get(m);
666            if (integer == null) {
667                return -1;
668            } else {
669                return integer;
670            }
671        }
672    }
673    /**
674     * Data structure which contains a list and can return the position of an
675     * element in the list in O(log N).
676     */
677    static class RankedTupleList {
678        final Map<List<Member>, Integer> map =
679            new HashMap<List<Member>, Integer>();
680
681        RankedTupleList(TupleList tupleList) {
682            int i = -1;
683            for (final List<Member> tupleMembers : tupleList.fix()) {
684                ++i;
685                final Integer value = map.put(tupleMembers, i);
686                if (value != null) {
687                    // The list already contained a value for this key -- put
688                    // it back.
689                    map.put(tupleMembers, value);
690                }
691            }
692        }
693
694        int indexOf(List<Member> tupleMembers) {
695            Integer integer = map.get(tupleMembers);
696            if (integer == null) {
697                return -1;
698            } else {
699                return integer;
700            }
701        }
702    }
703}
704
705// End RankFunDef.java