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) 2002-2005 Julian Hyde
008// Copyright (C) 2005-2012 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.*;
015import mondrian.mdx.ResolvedFunCall;
016import mondrian.olap.*;
017
018import java.util.AbstractList;
019import java.util.List;
020
021/**
022 * Definition of the <code>TopCount</code> and <code>BottomCount</code>
023 * MDX builtin functions.
024 *
025 * @author jhyde
026 * @since Mar 23, 2006
027 */
028class TopBottomCountFunDef extends FunDefBase {
029    boolean top;
030
031    static final MultiResolver TopCountResolver =
032        new MultiResolver(
033            "TopCount",
034            "TopCount(<Set>, <Count>[, <Numeric Expression>])",
035            "Returns a specified number of items from the top of a set, optionally ordering the set first.",
036            new String[]{"fxxnn", "fxxn"})
037        {
038            protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
039                return new TopBottomCountFunDef(dummyFunDef, true);
040            }
041        };
042
043    static final MultiResolver BottomCountResolver =
044        new MultiResolver(
045            "BottomCount",
046            "BottomCount(<Set>, <Count>[, <Numeric Expression>])",
047            "Returns a specified number of items from the bottom of a set, optionally ordering the set first.",
048            new String[]{"fxxnn", "fxxn"})
049        {
050            protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
051                return new TopBottomCountFunDef(dummyFunDef, false);
052            }
053        };
054
055    public TopBottomCountFunDef(FunDef dummyFunDef, final boolean top) {
056        super(dummyFunDef);
057        this.top = top;
058    }
059
060    public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) {
061        // Compile the member list expression. Ask for a mutable list, because
062        // we're going to sort it later.
063        final ListCalc listCalc =
064                compiler.compileList(call.getArg(0), true);
065        final IntegerCalc integerCalc =
066                compiler.compileInteger(call.getArg(1));
067        final Calc orderCalc =
068            call.getArgCount() > 2
069            ? compiler.compileScalar(call.getArg(2), true)
070            : null;
071        final int arity = call.getType().getArity();
072        return new AbstractListCalc(
073            call,
074            new Calc[]{listCalc, integerCalc, orderCalc})
075        {
076            public TupleList evaluateList(Evaluator evaluator) {
077                // Use a native evaluator, if more efficient.
078                // TODO: Figure this out at compile time.
079                SchemaReader schemaReader = evaluator.getSchemaReader();
080                NativeEvaluator nativeEvaluator =
081                    schemaReader.getNativeSetEvaluator(
082                        call.getFunDef(), call.getArgs(), evaluator, this);
083                if (nativeEvaluator != null) {
084                    return
085                        (TupleList) nativeEvaluator.execute(ResultStyle.LIST);
086                }
087
088                int n = integerCalc.evaluateInteger(evaluator);
089                if (n == 0 || n == mondrian.olap.fun.FunUtil.IntegerNull) {
090                    return TupleCollections.emptyList(arity);
091                }
092
093                TupleList list = listCalc.evaluateList(evaluator);
094                assert list.getArity() == arity;
095                if (list.isEmpty()) {
096                    return list;
097                }
098
099                if (orderCalc == null) {
100                    // REVIEW: Why require "instanceof AbstractList"?
101                    if (list instanceof AbstractList && list.size() <= n) {
102                        return list;
103                    } else {
104                        return list.subList(0, n);
105                    }
106                }
107
108                return partiallySortList(
109                    evaluator, list, hasHighCardDimension(list),
110                    Math.min(n, list.size()));
111            }
112
113            private TupleList partiallySortList(
114                Evaluator evaluator,
115                TupleList list,
116                boolean highCard,
117                int n)
118            {
119                assert list.size() > 0;
120                assert n <= list.size();
121                if (highCard) {
122                    // sort list in chunks, collect the results
123                    final int chunkSize = 6400; // what is this really?
124                    TupleList allChunkResults = TupleCollections.createList(
125                        arity);
126                    for (int i = 0, next; i < list.size(); i = next) {
127                        next = Math.min(i + chunkSize, list.size());
128                        final TupleList chunk = list.subList(i, next);
129                        TupleList chunkResult =
130                            partiallySortList(
131                                evaluator, chunk, false, n);
132                        allChunkResults.addAll(chunkResult);
133                    }
134                    // one last sort, to merge and cull
135                    return partiallySortList(
136                        evaluator, allChunkResults, false, n);
137                }
138
139                // normal case: no need for chunks
140                final int savepoint = evaluator.savepoint();
141                try {
142                    switch (list.getArity()) {
143                    case 1:
144                        final List<Member> members =
145                        partiallySortMembers(
146                            evaluator.push(),
147                            list.slice(0),
148                            orderCalc, n, top);
149                        return new UnaryTupleList(members);
150                    default:
151                        final List<List<Member>> tuples =
152                        partiallySortTuples(
153                            evaluator.push(),
154                            list,
155                            orderCalc, n, top);
156                        return new DelegatingTupleList(
157                            list.getArity(),
158                            tuples);
159                    }
160                } finally {
161                    evaluator.restore(savepoint);
162                }
163            }
164
165            public boolean dependsOn(Hierarchy hierarchy) {
166                return anyDependsButFirst(getCalcs(), hierarchy);
167            }
168
169            private boolean hasHighCardDimension(TupleList l) {
170                final List<Member> trial = l.get(0);
171                for (Member m : trial) {
172                    if (m.getHierarchy().getDimension().isHighCardinality()) {
173                        return true;
174                    }
175                }
176                return false;
177            }
178        };
179    }
180}
181
182// End TopBottomCountFunDef.java