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) 2004-2005 Julian Hyde
008// Copyright (C) 2005-2011 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.*;
017
018import java.util.*;
019
020/**
021 * Definition of the <code>Order</code> MDX function.
022 *
023 * @author jhyde
024 * @since Mar 23, 2006
025 */
026class OrderFunDef extends FunDefBase {
027
028    static final ResolverImpl Resolver = new ResolverImpl();
029
030    public OrderFunDef(ResolverBase resolverBase, int type, int[] types) {
031        super(resolverBase, type, types);
032    }
033
034    public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) {
035        final IterCalc listCalc = compiler.compileIter(call.getArg(0));
036        List<SortKeySpec> keySpecList = new ArrayList<SortKeySpec>();
037        buildKeySpecList(keySpecList, call, compiler);
038        final int keySpecCount = keySpecList.size();
039        Calc[] calcList = new Calc[keySpecCount + 1]; // +1 for the listCalc
040        calcList[0] = listCalc;
041
042        assert keySpecCount >= 1;
043        final Calc expCalc = keySpecList.get(0).getKey();
044        calcList[1] = expCalc;
045        if (keySpecCount == 1) {
046            if (expCalc.isWrapperFor(MemberValueCalc.class)
047                || expCalc.isWrapperFor(MemberArrayValueCalc.class))
048            {
049                List<MemberCalc> constantList = new ArrayList<MemberCalc>();
050                List<MemberCalc> variableList = new ArrayList<MemberCalc>();
051                final MemberCalc[] calcs =
052                    (MemberCalc[]) ((AbstractCalc) expCalc).getCalcs();
053                for (MemberCalc memberCalc : calcs) {
054                    if (memberCalc.isWrapperFor(ConstantCalc.class)
055                        && !listCalc.dependsOn(
056                            memberCalc.getType().getHierarchy()))
057                    {
058                        constantList.add(memberCalc);
059                    } else {
060                        variableList.add(memberCalc);
061                    }
062                }
063                if (constantList.isEmpty()) {
064                    // All members are non-constant -- cannot optimize
065                } else if (variableList.isEmpty()) {
066                    // All members are constant. Optimize by setting entire
067                    // context first.
068                    calcList[1] = new ValueCalc(
069                        new DummyExp(expCalc.getType()));
070                    return new ContextCalc(
071                        calcs,
072                        new CalcImpl(
073                            call, calcList, keySpecList));
074                } else {
075                    // Some members are constant. Evaluate these before
076                    // evaluating the list expression.
077                    calcList[1] = MemberValueCalc.create(
078                        new DummyExp(expCalc.getType()),
079                        variableList.toArray(
080                            new MemberCalc[variableList.size()]),
081                        compiler.getEvaluator()
082                            .mightReturnNullForUnrelatedDimension());
083                    return new ContextCalc(
084                        constantList.toArray(
085                            new MemberCalc[constantList.size()]),
086                        new CalcImpl(
087                            call, calcList, keySpecList));
088                }
089            }
090        }
091        for (int i = 1; i < keySpecCount; i++) {
092            final Calc expCalcs = keySpecList.get(i).getKey();
093            calcList[i + 1] = expCalcs;
094        }
095        return new CalcImpl(call, calcList, keySpecList);
096    }
097
098    private void buildKeySpecList(
099        List<SortKeySpec> keySpecList,
100        ResolvedFunCall call,
101        ExpCompiler compiler)
102    {
103        final int argCount = call.getArgs().length;
104        int j = 1; // args[0] is the input set
105        Calc key;
106        Flag dir;
107        Exp arg;
108        while (j < argCount) {
109            arg = call.getArg(j);
110            key = compiler.compileScalar(arg, true);
111            j++;
112            if ((j >= argCount)
113                || (call.getArg(j).getCategory() != Category.Symbol))
114            {
115                dir = Flag.ASC;
116            } else {
117                dir = getLiteralArg(call, j, Flag.ASC, Flag.class);
118                j++;
119            }
120            keySpecList.add(new SortKeySpec(key, dir));
121        }
122    }
123
124    private interface CalcWithDual extends Calc {
125        public TupleList evaluateDual(
126            Evaluator rootEvaluator,
127            Evaluator subEvaluator);
128    }
129
130    private static class CalcImpl
131        extends AbstractListCalc
132        implements CalcWithDual
133    {
134        private final IterCalc iterCalc;
135        private final Calc sortKeyCalc;
136        private final List<SortKeySpec> keySpecList;
137        private final int originalKeySpecCount;
138        private final int arity;
139
140        public CalcImpl(
141            ResolvedFunCall call,
142            Calc[] calcList,
143            List<SortKeySpec> keySpecList)
144        {
145            super(call, calcList);
146//            assert iterCalc.getResultStyle() == ResultStyle.MUTABLE_LIST;
147            this.iterCalc = (IterCalc) calcList[0];
148            this.sortKeyCalc = calcList[1];
149            this.keySpecList = keySpecList;
150            this.originalKeySpecCount = keySpecList.size();
151            this.arity = getType().getArity();
152        }
153
154        public TupleList evaluateDual(
155            Evaluator rootEvaluator, Evaluator subEvaluator)
156        {
157            assert originalKeySpecCount == 1;
158            final TupleIterable iterable =
159                iterCalc.evaluateIterable(rootEvaluator);
160            // REVIEW: If iterable happens to be a list, we'd like to pass it,
161            // but we cannot yet guarantee that it is mutable.
162            final TupleList list =
163                iterable instanceof TupleList && false
164                    ? (TupleList) iterable
165                    : null;
166            Util.discard(iterCalc.getResultStyle());
167            Flag sortKeyDir = keySpecList.get(0).getDirection();
168            final TupleList tupleList;
169            final int savepoint = subEvaluator.savepoint();
170            try {
171                subEvaluator.setNonEmpty(false);
172                if (arity == 1) {
173                    tupleList =
174                        new UnaryTupleList(
175                            sortMembers(
176                                subEvaluator,
177                                iterable.slice(0),
178                                list == null
179                                ? null
180                                    : list.slice(0),
181                                    sortKeyCalc,
182                                    sortKeyDir.descending,
183                                    sortKeyDir.brk));
184                } else {
185                    tupleList = sortTuples(
186                        subEvaluator,
187                        iterable,
188                        list,
189                        sortKeyCalc,
190                        sortKeyDir.descending,
191                        sortKeyDir.brk,
192                        arity);
193                }
194                return tupleList;
195            } finally {
196                subEvaluator.restore(savepoint);
197            }
198        }
199
200        public TupleList evaluateList(Evaluator evaluator) {
201            final TupleIterable iterable =
202                iterCalc.evaluateIterable(evaluator);
203            // REVIEW: If iterable happens to be a list, we'd like to pass it,
204            // but we cannot yet guarantee that it is mutable.
205            final TupleList list =
206                iterable instanceof TupleList && false
207                    ? (TupleList) iterable
208                    : null;
209            // go by size of keySpecList before purging
210            if (originalKeySpecCount == 1) {
211                Flag sortKeyDir = keySpecList.get(0).getDirection();
212                final TupleList tupleList;
213                final int savepoint = evaluator.savepoint();
214                try {
215                    evaluator.setNonEmpty(false);
216                    if (arity == 1) {
217                        tupleList =
218                            new UnaryTupleList(
219                                sortMembers(
220                                    evaluator,
221                                    iterable.slice(0),
222                                    list == null ? null : list.slice(0),
223                                        sortKeyCalc,
224                                        sortKeyDir.descending,
225                                        sortKeyDir.brk));
226                    } else {
227                        tupleList =
228                            sortTuples(
229                                evaluator,
230                                iterable,
231                                list,
232                                sortKeyCalc,
233                                sortKeyDir.descending,
234                                sortKeyDir.brk,
235                                arity);
236                    }
237                    return tupleList;
238                } finally {
239                    evaluator.restore(savepoint);
240                }
241            } else {
242                purgeKeySpecList(keySpecList, list);
243                if (keySpecList.isEmpty()) {
244                    return list;
245                }
246                final TupleList tupleList;
247                final int savepoint = evaluator.savepoint();
248                try {
249                    evaluator.setNonEmpty(false);
250                    if (arity == 1) {
251                        tupleList =
252                            new UnaryTupleList(
253                                sortMembers(
254                                    evaluator,
255                                    iterable.slice(0),
256                                    list == null ? null : list.slice(0),
257                                        keySpecList));
258                    } else {
259                        tupleList =
260                            sortTuples(
261                                evaluator,
262                                iterable,
263                                list,
264                                keySpecList,
265                                arity);
266                    }
267                    return tupleList;
268                } finally {
269                    evaluator.restore(savepoint);
270                }
271            }
272        }
273
274        public void collectArguments(Map<String, Object> arguments) {
275            super.collectArguments(arguments);
276
277            // only good for original Order syntax
278            assert originalKeySpecCount == 1;
279            Flag sortKeyDir = keySpecList.get(0).getDirection();
280            arguments.put(
281                "direction",
282                (sortKeyDir.descending
283                 ? (sortKeyDir.brk ? Flag.BDESC : Flag.DESC)
284                 : (sortKeyDir.brk ? Flag.BASC : Flag.ASC)));
285        }
286
287        public boolean dependsOn(Hierarchy hierarchy) {
288            return anyDependsButFirst(getCalcs(), hierarchy);
289        }
290
291        private void purgeKeySpecList(
292            List<SortKeySpec> keySpecList,
293            TupleList list)
294        {
295            if (list == null || list.isEmpty()) {
296                return;
297            }
298            if (keySpecList.size() == 1) {
299                return;
300            }
301            List<Hierarchy> listHierarchies =
302                new ArrayList<Hierarchy>(list.getArity());
303            for (Member member : list.get(0)) {
304                listHierarchies.add(member.getHierarchy());
305            }
306            // do not sort (remove sort key spec from the list) if
307            // 1. <member_value_expression> evaluates to a member from a
308            // level/dimension which is not used in the first argument
309            // 2. <member_value_expression> evaluates to the same member for
310            // all cells; for example, a report showing all quarters of
311            // year 1998 will not be sorted if the sort key is on the constant
312            // member [1998].[Q1]
313            ListIterator<SortKeySpec> iter = keySpecList.listIterator();
314            while (iter.hasNext()) {
315                SortKeySpec key = iter.next();
316                Calc expCalc = key.getKey();
317                if (expCalc instanceof MemberOrderKeyFunDef.CalcImpl) {
318                    Calc[] calcs =
319                        ((MemberOrderKeyFunDef.CalcImpl) expCalc).getCalcs();
320                    MemberCalc memberCalc = (MemberCalc) calcs[0];
321                    if (memberCalc instanceof ConstantCalc
322                        || !listHierarchies.contains(
323                            memberCalc.getType().getHierarchy()))
324                    {
325                        iter.remove();
326                    }
327                }
328            }
329        }
330    }
331
332    private static class ContextCalc extends GenericIterCalc {
333        private final MemberCalc[] memberCalcs;
334        private final CalcWithDual calc;
335        private final Member[] members; // workspace
336
337        protected ContextCalc(MemberCalc[] memberCalcs, CalcWithDual calc) {
338            super(new DummyExp(calc.getType()), xx(memberCalcs, calc));
339            this.memberCalcs = memberCalcs;
340            this.calc = calc;
341            this.members = new Member[memberCalcs.length];
342        }
343
344        private static Calc[] xx(
345            MemberCalc[] memberCalcs, CalcWithDual calc)
346        {
347            Calc[] calcs = new Calc[memberCalcs.length + 1];
348            System.arraycopy(memberCalcs, 0, calcs, 0, memberCalcs.length);
349            calcs[calcs.length - 1] = calc;
350            return calcs;
351        }
352
353        public Object evaluate(Evaluator evaluator) {
354            // Evaluate each of the members, and set as context in the
355            // sub-evaluator.
356            for (int i = 0; i < memberCalcs.length; i++) {
357                members[i] = memberCalcs[i].evaluateMember(evaluator);
358            }
359            final Evaluator subEval = evaluator.push(members);
360            // Evaluate the expression in the new context.
361            return calc.evaluateDual(evaluator, subEval);
362        }
363
364        public boolean dependsOn(Hierarchy hierarchy) {
365            if (anyDepends(memberCalcs, hierarchy)) {
366                return true;
367            }
368            // Member calculations generate members, which mask the actual
369            // expression from the inherited context.
370            for (MemberCalc memberCalc : memberCalcs) {
371                if (memberCalc.getType().usesHierarchy(hierarchy, true)) {
372                    return false;
373                }
374            }
375            return calc.dependsOn(hierarchy);
376        }
377
378        public ResultStyle getResultStyle() {
379            return calc.getResultStyle();
380        }
381    }
382
383    private static class ResolverImpl extends ResolverBase {
384        private final String[] reservedWords;
385        static int[] argTypes;
386
387        private ResolverImpl() {
388            super(
389                "Order",
390                "Order(<Set> {, <Key Specification>}...)",
391                "Arranges members of a set, optionally preserving or breaking the hierarchy.",
392                Syntax.Function);
393            this.reservedWords = Flag.getNames();
394        }
395
396        public FunDef resolve(
397            Exp[] args,
398            Validator validator,
399            List<Conversion> conversions)
400        {
401            argTypes = new int[args.length];
402
403            if (args.length < 2) {
404                return null;
405            }
406            // first arg must be a set
407            if (!validator.canConvert(0, args[0], Category.Set, conversions)) {
408                return null;
409            }
410            argTypes[0] = Category.Set;
411            // after fist args, should be: value [, symbol]
412            int i = 1;
413            while (i < args.length) {
414                if (!validator.canConvert(
415                        i, args[i], Category.Value, conversions))
416                {
417                    return null;
418                } else {
419                    argTypes[i] = Category.Value;
420                    i++;
421                }
422                // if symbol is not specified, skip to the next
423                if ((i == args.length)) {
424                    //done, will default last arg to ASC
425                } else {
426                    if (!validator.canConvert(
427                            i, args[i], Category.Symbol, conversions))
428                    {
429                        // continue, will default sort flag for prev arg to ASC
430                    } else {
431                        argTypes[i] = Category.Symbol;
432                        i++;
433                    }
434                }
435            }
436            return new OrderFunDef(this, Category.Set, argTypes);
437        }
438
439        public String[] getReservedWords() {
440            if (reservedWords != null) {
441                return reservedWords;
442            }
443            return super.getReservedWords();
444        }
445    }
446}
447
448// End OrderFunDef.java