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-2011 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.calc.*;
014import mondrian.calc.impl.*;
015import mondrian.mdx.*;
016import mondrian.olap.*;
017import mondrian.olap.type.*;
018import mondrian.rolap.RolapEvaluator;
019import mondrian.rolap.RolapUtil;
020import mondrian.util.CartesianProductList;
021
022import java.util.*;
023
024/**
025 * Definition of the <code>CrossJoin</code> MDX function.
026 *
027 * @author jhyde
028 * @since Mar 23, 2006
029 */
030public class CrossJoinFunDef extends FunDefBase {
031    static final ReflectiveMultiResolver Resolver =
032        new ReflectiveMultiResolver(
033            "Crossjoin",
034            "Crossjoin(<Set1>, <Set2>)",
035            "Returns the cross product of two sets.",
036            new String[]{"fxxx"},
037            CrossJoinFunDef.class);
038
039    static final StarCrossJoinResolver StarResolver =
040        new StarCrossJoinResolver();
041
042    private static int counterTag = 0;
043
044    // used to tell the difference between crossjoin expressions.
045    private final int ctag = counterTag++;
046
047    public CrossJoinFunDef(FunDef dummyFunDef) {
048        super(dummyFunDef);
049    }
050
051    public Type getResultType(Validator validator, Exp[] args) {
052        // CROSSJOIN(<Set1>,<Set2>) has type [Hie1] x [Hie2].
053        List<MemberType> list = new ArrayList<MemberType>();
054        for (Exp arg : args) {
055            final Type type = arg.getType();
056            if (type instanceof SetType) {
057                addTypes(type, list);
058            } else if (getName().equals("*")) {
059                // The "*" form of CrossJoin is lenient: args can be either
060                // members/tuples or sets.
061                addTypes(type, list);
062            } else {
063                throw Util.newInternal("arg to crossjoin must be a set");
064            }
065        }
066        final MemberType[] types = list.toArray(new MemberType[list.size()]);
067        TupleType.checkHierarchies(types);
068        final TupleType tupleType = new TupleType(types);
069        return new SetType(tupleType);
070    }
071
072    /**
073     * Adds a type to a list of types. If type is a {@link TupleType}, does so
074     * recursively.
075     *
076     * @param type Type to add to list
077     * @param list List of types to add to
078     */
079    private static void addTypes(final Type type, List<MemberType> list) {
080        if (type instanceof SetType) {
081            SetType setType = (SetType) type;
082            addTypes(setType.getElementType(), list);
083        } else if (type instanceof TupleType) {
084            TupleType tupleType = (TupleType) type;
085            for (Type elementType : tupleType.elementTypes) {
086                addTypes(elementType, list);
087            }
088        } else if (type instanceof MemberType) {
089            list.add((MemberType) type);
090        } else {
091            throw Util.newInternal("Unexpected type: " + type);
092        }
093    }
094
095    public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) {
096        // What is the desired return type?
097        for (ResultStyle r : compiler.getAcceptableResultStyles()) {
098            switch (r) {
099            case ITERABLE:
100            case ANY:
101                // Consumer wants ITERABLE or ANY
102                    return compileCallIterable(call, compiler);
103            case LIST:
104                // Consumer wants (immutable) LIST
105                return compileCallImmutableList(call, compiler);
106            case MUTABLE_LIST:
107                // Consumer MUTABLE_LIST
108                return compileCallMutableList(call, compiler);
109            }
110        }
111        throw ResultStyleException.generate(
112            ResultStyle.ITERABLE_LIST_MUTABLELIST_ANY,
113            compiler.getAcceptableResultStyles());
114    }
115
116    ///////////////////////////////////////////////////////////////////////////
117    ///////////////////////////////////////////////////////////////////////////
118    // Iterable
119    ///////////////////////////////////////////////////////////////////////////
120    ///////////////////////////////////////////////////////////////////////////
121
122    protected IterCalc compileCallIterable(
123        final ResolvedFunCall call,
124        ExpCompiler compiler)
125    {
126        final Calc calc1 = toIter(compiler, call.getArg(0));
127        final Calc calc2 = toIter(compiler, call.getArg(1));
128        Calc[] calcs = new Calc[] {calc1, calc2};
129        // The Calcs, 1 and 2, can be of type: Member or Member[] and
130        // of ResultStyle: ITERABLE, LIST or MUTABLE_LIST, but
131        // LIST and MUTABLE_LIST are treated the same; so
132        // there are 16 possible combinations - sweet.
133
134        // Check returned calc ResultStyles
135        checkIterListResultStyles(calc1);
136        checkIterListResultStyles(calc2);
137
138        return new CrossJoinIterCalc(call, calcs);
139    }
140
141    private Calc toIter(ExpCompiler compiler, final Exp exp) {
142        // Want iterable, immutable list or mutable list in that order
143        // It is assumed that an immutable list is easier to get than
144        // a mutable list.
145        final Type type = exp.getType();
146        if (type instanceof SetType) {
147            // this can return an IterCalc or ListCalc
148            return compiler.compileAs(
149                exp,
150                null,
151                ResultStyle.ITERABLE_LIST_MUTABLELIST);
152        } else {
153            // this always returns an IterCalc
154            return new SetFunDef.ExprIterCalc(
155                new DummyExp(new SetType(type)),
156                new Exp[] {exp},
157                compiler,
158                ResultStyle.ITERABLE_LIST_MUTABLELIST);
159        }
160    }
161
162    class CrossJoinIterCalc extends AbstractIterCalc
163    {
164        CrossJoinIterCalc(ResolvedFunCall call, Calc[] calcs) {
165            super(call, calcs);
166        }
167
168        public TupleIterable evaluateIterable(Evaluator evaluator) {
169            ResolvedFunCall call = (ResolvedFunCall) exp;
170            // Use a native evaluator, if more efficient.
171            // TODO: Figure this out at compile time.
172            SchemaReader schemaReader = evaluator.getSchemaReader();
173            NativeEvaluator nativeEvaluator =
174                schemaReader.getNativeSetEvaluator(
175                    call.getFunDef(), call.getArgs(), evaluator, this);
176            if (nativeEvaluator != null) {
177                return (TupleIterable)
178                    nativeEvaluator.execute(ResultStyle.ITERABLE);
179            }
180
181            Calc[] calcs = getCalcs();
182            IterCalc calc1 = (IterCalc) calcs[0];
183            IterCalc calc2 = (IterCalc) calcs[1];
184
185            TupleIterable o1 = calc1.evaluateIterable(evaluator);
186            if (o1 instanceof TupleList) {
187                TupleList l1 = (TupleList) o1;
188                l1 = nonEmptyOptimizeList(evaluator, l1, call);
189                if (l1.isEmpty()) {
190                    return TupleCollections.emptyList(getType().getArity());
191                }
192                o1 = l1;
193            }
194
195            TupleIterable o2 = calc2.evaluateIterable(evaluator);
196            if (o2 instanceof TupleList) {
197                TupleList l2 = (TupleList) o2;
198                l2 = nonEmptyOptimizeList(evaluator, l2, call);
199                if (l2.isEmpty()) {
200                    return TupleCollections.emptyList(getType().getArity());
201                }
202                o2 = l2;
203            }
204
205            return makeIterable(o1, o2);
206        }
207
208        protected TupleIterable makeIterable(
209            final TupleIterable it1,
210            final TupleIterable it2)
211        {
212            // There is no knowledge about how large either it1 ore it2
213            // are or how many null members they might have, so all
214            // one can do is iterate across them:
215            // iterate across it1 and for each member iterate across it2
216
217            return new AbstractTupleIterable(it1.getArity() + it2.getArity()) {
218                public TupleCursor tupleCursor() {
219                    return new AbstractTupleCursor(getArity()) {
220                        final TupleCursor i1 = it1.tupleCursor();
221                        final int arity1 = i1.getArity();
222                        TupleCursor i2 =
223                            TupleCollections.emptyList(1).tupleCursor();
224                        final Member[] members = new Member[arity];
225
226                        public boolean forward() {
227                            if (i2.forward()) {
228                                return true;
229                            }
230                            while (i1.forward()) {
231                                i2 = it2.tupleCursor();
232                                if (i2.forward()) {
233                                    return true;
234                                }
235                            }
236                            return false;
237                        }
238
239                        public List<Member> current() {
240                            i1.currentToArray(members, 0);
241                            i2.currentToArray(members, arity1);
242                            return Util.flatList(members);
243                        }
244
245                        @Override
246                        public Member member(int column) {
247                            if (column < arity1) {
248                                return i1.member(column);
249                            } else {
250                                return i2.member(column - arity1);
251                            }
252                        }
253
254                        @Override
255                        public void setContext(Evaluator evaluator) {
256                            i1.setContext(evaluator);
257                            i2.setContext(evaluator);
258                        }
259
260                        @Override
261                        public void currentToArray(
262                            Member[] members,
263                            int offset)
264                        {
265                            i1.currentToArray(members, offset);
266                            i2.currentToArray(members, offset + arity1);
267                        }
268                    };
269                }
270            };
271        }
272    }
273
274    ///////////////////////////////////////////////////////////////////////////
275    // Immutable List
276    ///////////////////////////////////////////////////////////////////////////
277
278    protected ListCalc compileCallImmutableList(
279        final ResolvedFunCall call,
280        ExpCompiler compiler)
281    {
282        final ListCalc listCalc1 = toList(compiler, call.getArg(0));
283        final ListCalc listCalc2 = toList(compiler, call.getArg(1));
284        Calc[] calcs = new Calc[] {listCalc1, listCalc2};
285        // The Calcs, 1 and 2, can be of type: Member or Member[] and
286        // of ResultStyle: LIST or MUTABLE_LIST.
287        // Since we want an immutable list as the result, it does not
288        // matter whether the Calc list are of type
289        // LIST and MUTABLE_LIST - they are treated the same; so
290        // there are 4 possible combinations - even sweeter.
291
292        // Check returned calc ResultStyles
293        checkListResultStyles(listCalc1);
294        checkListResultStyles(listCalc2);
295
296        return new ImmutableListCalc(call, calcs);
297    }
298
299    /**
300     * Compiles an expression to list (or mutable list) format. Never returns
301     * null.
302     *
303     * @param compiler Compiler
304     * @param exp Expression
305     * @return Compiled expression that yields a list or mutable list
306     */
307    private ListCalc toList(ExpCompiler compiler, final Exp exp) {
308        // Want immutable list or mutable list in that order
309        // It is assumed that an immutable list is easier to get than
310        // a mutable list.
311        final Type type = exp.getType();
312        if (type instanceof SetType) {
313            final Calc calc = compiler.compileAs(
314                exp, null, ResultStyle.LIST_MUTABLELIST);
315            if (calc == null) {
316                return compiler.compileList(exp, false);
317            }
318            return (ListCalc) calc;
319        } else {
320            return new SetFunDef.SetListCalc(
321                new DummyExp(new SetType(type)),
322                new Exp[] {exp},
323                compiler,
324                ResultStyle.LIST_MUTABLELIST);
325        }
326    }
327
328    abstract class BaseListCalc extends AbstractListCalc {
329        protected BaseListCalc(
330            ResolvedFunCall call,
331            Calc[] calcs,
332            boolean mutable)
333        {
334            super(call, calcs, mutable);
335        }
336
337        public TupleList evaluateList(Evaluator evaluator) {
338            ResolvedFunCall call = (ResolvedFunCall) exp;
339            // Use a native evaluator, if more efficient.
340            // TODO: Figure this out at compile time.
341            SchemaReader schemaReader = evaluator.getSchemaReader();
342            NativeEvaluator nativeEvaluator =
343                schemaReader.getNativeSetEvaluator(
344                    call.getFunDef(), call.getArgs(), evaluator, this);
345            if (nativeEvaluator != null) {
346                return (TupleList) nativeEvaluator.execute(ResultStyle.LIST);
347            }
348
349            Calc[] calcs = getCalcs();
350            ListCalc listCalc1 = (ListCalc) calcs[0];
351            ListCalc listCalc2 = (ListCalc) calcs[1];
352
353            TupleList l1 = listCalc1.evaluateList(evaluator);
354            TupleList l2 = listCalc2.evaluateList(evaluator);
355
356            l1 = nonEmptyOptimizeList(evaluator, l1, call);
357            if (l1.isEmpty()) {
358                return TupleCollections.emptyList(
359                    l1.getArity() + l2.getArity());
360            }
361            l2 = nonEmptyOptimizeList(evaluator, l2, call);
362            if (l2.isEmpty()) {
363                return TupleCollections.emptyList(
364                    l1.getArity() + l2.getArity());
365            }
366
367            return makeList(l1, l2);
368        }
369
370        protected abstract TupleList makeList(TupleList l1, TupleList l2);
371    }
372
373    class ImmutableListCalc
374        extends BaseListCalc
375    {
376        ImmutableListCalc(
377            ResolvedFunCall call, Calc[] calcs)
378        {
379            super(call, calcs, false);
380        }
381
382        protected TupleList makeList(final TupleList l1, final TupleList l2) {
383            final int arity = l1.getArity() + l2.getArity();
384            return new DelegatingTupleList(
385                arity,
386                new AbstractList<List<Member>>() {
387                    final List<List<List<Member>>> lists =
388                        Arrays.<List<List<Member>>>asList(
389                            l1, l2);
390                    final Member[] members = new Member[arity];
391
392                    final CartesianProductList cartesianProductList =
393                        new CartesianProductList<List<Member>>(
394                            lists);
395
396                    @Override
397                    public List<Member> get(int index) {
398                        cartesianProductList.getIntoArray(index, members);
399                        return Util.flatList(members);
400                    }
401
402                    @Override
403                    public int size() {
404                        return cartesianProductList.size();
405                    }
406                });
407        }
408    }
409
410    protected ListCalc compileCallMutableList(
411        final ResolvedFunCall call,
412        ExpCompiler compiler)
413    {
414        final ListCalc listCalc1 = toList(compiler, call.getArg(0));
415        final ListCalc listCalc2 = toList(compiler, call.getArg(1));
416
417        Calc[] calcs = new Calc[] {listCalc1, listCalc2};
418        // The Calcs, 1 and 2, can be of type: Member or Member[] and
419        // of ResultStyle: LIST or MUTABLE_LIST.
420        // Since we want an mutable list as the result, it does not
421        // matter whether the Calc list are of type
422        // LIST and MUTABLE_LIST - they are treated the same,
423        // regardless of type, one must materialize the result list; so
424        // there are 4 possible combinations - even sweeter.
425
426        // Check returned calc ResultStyles
427        checkListResultStyles(listCalc1);
428        checkListResultStyles(listCalc2);
429
430        return new MutableListCalc(call, calcs);
431    }
432
433    class MutableListCalc extends BaseListCalc
434    {
435        MutableListCalc(ResolvedFunCall call, Calc[] calcs)
436        {
437            super(call, calcs, true);
438        }
439
440        @SuppressWarnings({"unchecked"})
441        protected TupleList makeList(final TupleList l1, final TupleList l2) {
442            final int arity = l1.getArity() + l2.getArity();
443            final List<Member> members =
444                new ArrayList<Member>(arity * l1.size() * l2.size());
445            for (List<Member> ma1 : l1) {
446                for (List<Member> ma2 : l2) {
447                    members.addAll(ma1);
448                    members.addAll(ma2);
449                }
450            }
451            return new ListTupleList(arity, members);
452        }
453    }
454
455    protected TupleList nonEmptyOptimizeList(
456        Evaluator evaluator,
457        TupleList list,
458        ResolvedFunCall call)
459    {
460        int opSize = MondrianProperties.instance().CrossJoinOptimizerSize.get();
461        if (list.isEmpty()) {
462            return list;
463        }
464        try {
465            final Object o = list.get(0);
466            if (o instanceof Member) {
467                // Cannot optimize high cardinality dimensions
468                if (((Member)o).getDimension().isHighCardinality()) {
469                    return list;
470                }
471            }
472        } catch (IndexOutOfBoundsException ioobe) {
473            return TupleCollections.emptyList(list.getArity());
474        }
475        int size = list.size();
476
477        if (size > opSize && evaluator.isNonEmpty()) {
478            // instead of overflow exception try to further
479            // optimize nonempty(crossjoin(a,b)) ==
480            // nonempty(crossjoin(nonempty(a),nonempty(b))
481            final int missCount = evaluator.getMissCount();
482
483            list = nonEmptyList(evaluator, list, call);
484            size = list.size();
485            // list may be empty after nonEmpty optimization
486            if (size == 0) {
487                return TupleCollections.emptyList(list.getArity());
488            }
489            final int missCount2 = evaluator.getMissCount();
490            final int puntMissCountListSize = 1000;
491            if (missCount2 > missCount && size > puntMissCountListSize) {
492                // We've hit some cells which are not in the cache. They
493                // registered as non-empty, but we won't really know until
494                // we've populated the cache. The cartesian product is still
495                // huge, so let's quit now, and try again after the cache
496                // has been loaded.
497                // Return an empty list short circuits higher level
498                // evaluation poping one all the way to the top.
499                return TupleCollections.emptyList(list.getArity());
500            }
501        }
502        return list;
503    }
504
505    public static TupleList mutableCrossJoin(
506        TupleList list1,
507        TupleList list2)
508    {
509        return mutableCrossJoin(Arrays.asList(list1, list2));
510    }
511
512    public static TupleList mutableCrossJoin(
513        List<TupleList> lists)
514    {
515        long size = 1;
516        int arity = 0;
517        for (TupleList list : lists) {
518            size *= (long) list.size();
519            arity += list.getArity();
520        }
521        if (size == 0L) {
522            return TupleCollections.emptyList(arity);
523        }
524
525        // Optimize nonempty(crossjoin(a,b)) ==
526        //  nonempty(crossjoin(nonempty(a),nonempty(b))
527
528        // FIXME: If we're going to apply a NON EMPTY constraint later, it's
529        // possible that the ultimate result will be much smaller.
530
531        Util.checkCJResultLimit(size);
532
533        // Now we can safely cast size to an integer. It still might be very
534        // large - which means we're allocating a huge array which we might
535        // pare down later by applying NON EMPTY constraints - which is a
536        // concern.
537        List<Member> result = new ArrayList<Member>((int) size * arity);
538
539        final Member[] partialArray = new Member[arity];
540        final List<Member> partial = Arrays.asList(partialArray);
541        cartesianProductRecurse(0, lists, partial, partialArray, 0, result);
542        return new ListTupleList(arity, result);
543    }
544
545    private static void cartesianProductRecurse(
546        int i,
547        List<TupleList> lists,
548        List<Member> partial,
549        Member[] partialArray,
550        int partialSize,
551        List<Member> result)
552    {
553        final TupleList tupleList = lists.get(i);
554        final int partialSizeNext = partialSize + tupleList.getArity();
555        final int iNext = i + 1;
556        final TupleCursor cursor = tupleList.tupleCursor();
557        while (cursor.forward()) {
558            cursor.currentToArray(partialArray, partialSize);
559            if (i == lists.size() - 1) {
560                result.addAll(partial);
561            } else {
562                cartesianProductRecurse(
563                    iNext, lists, partial, partialArray, partialSizeNext,
564                    result);
565            }
566        }
567    }
568
569    /**
570     * Visitor class used to locate a resolved function call within an
571     * expression
572     */
573    private static class ResolvedFunCallFinder
574        extends MdxVisitorImpl
575    {
576        private final ResolvedFunCall call;
577        public boolean found;
578        private final Set<Member> activeMembers = new HashSet<Member>();
579
580        public ResolvedFunCallFinder(ResolvedFunCall call)
581        {
582            this.call = call;
583            found = false;
584        }
585
586        public Object visit(ResolvedFunCall funCall)
587        {
588            if (funCall == call) {
589                found = true;
590            }
591            return null;
592        }
593
594        public Object visit(MemberExpr memberExpr) {
595            Member member = memberExpr.getMember();
596            if (member.isCalculated()) {
597                if (activeMembers.add(member)) {
598                    Exp memberExp = member.getExpression();
599                    memberExp.accept(this);
600                    activeMembers.remove(member);
601                }
602            }
603            return null;
604        }
605    }
606
607    /**
608     * Traverses the function call tree of
609     * the non empty crossjoin function and populates the queryMeasureSet
610     * with base measures
611     */
612    private static class MeasureVisitor extends MdxVisitorImpl {
613
614        private final Set<Member> queryMeasureSet;
615        private final ResolvedFunCallFinder finder;
616        private final Set<Member> activeMeasures = new HashSet<Member>();
617
618        /**
619         * Creates a MeasureVisitor.
620         *
621         * @param queryMeasureSet Set of measures in query
622         *
623         * @param crossJoinCall Measures referencing this call should be
624         * excluded from the list of measures found
625         */
626        MeasureVisitor(
627            Set<Member> queryMeasureSet,
628            ResolvedFunCall crossJoinCall)
629        {
630            this.queryMeasureSet = queryMeasureSet;
631            this.finder = new ResolvedFunCallFinder(crossJoinCall);
632        }
633
634        public Object visit(ParameterExpr parameterExpr) {
635            final Parameter parameter = parameterExpr.getParameter();
636            final Type type = parameter.getType();
637            if (type instanceof mondrian.olap.type.MemberType) {
638                final Object value = parameter.getValue();
639                if (value instanceof Member) {
640                    final Member member = (Member) value;
641                    process(member);
642                }
643            }
644
645            return null;
646        }
647
648        public Object visit(MemberExpr memberExpr) {
649            Member member = memberExpr.getMember();
650            process(member);
651            return null;
652        }
653
654        private void process(final Member member) {
655            if (member.isMeasure()) {
656                if (member.isCalculated()) {
657                    if (activeMeasures.add(member)) {
658                        Exp exp = member.getExpression();
659                        finder.found = false;
660                        exp.accept(finder);
661                        if (! finder.found) {
662                            exp.accept(this);
663                        }
664                        activeMeasures.remove(member);
665                    }
666                } else {
667                    queryMeasureSet.add(member);
668                }
669            }
670        }
671    }
672
673    /**
674     * This is the entry point to the crossjoin non-empty optimizer code.
675     *
676     * <p>What one wants to determine is for each individual Member of the input
677     * parameter list, a 'List-Member', whether across a slice there is any
678     * data.
679     *
680     * <p>But what data?
681     *
682     * <p>For Members other than those in the list, the 'non-List-Members',
683     * one wants to consider
684     * all data across the scope of these other Members. For instance, if
685     * Time is not a List-Member, then one wants to consider data
686     * across All Time. Or, if Customer is not a List-Member, then
687     * look at data across All Customers. The theory here, is if there
688     * is no data for a particular Member of the list where all other
689     * Members not part of the list are span their complete hierarchy, then
690     * there is certainly no data for Members of that Hierarchy at a
691     * more specific Level (more on this below).
692     *
693     * <p>When a Member that is a non-List-Member is part of a Hierarchy
694     * that has an
695     * All Member (hasAll="true"), then its very easy to make sure that
696     * the All Member is used during the optimization.
697     * If a non-List-Member is part of a Hierarchy that does not have
698     * an All Member, then one must, in fact, iterate over all top-level
699     * Members of the Hierarchy!!! - otherwise a List-Member might
700     * be excluded because the optimization code was not looking everywhere.
701     *
702     * <p>Concerning default Members for those Hierarchies for the
703     * non-List-Members, ignore them. What is wanted is either the
704     * All Member or one must iterate across all top-level Members, what
705     * happens to be the default Member of the Hierarchy is of no relevant.
706     *
707     * <p>The Measures Hierarchy has special considerations. First, there is
708     * no All Measure. But, certainly one need only involve Measures
709     * that are actually in the query... yes and no. For Calculated Measures
710     * one must also get all of the non-Calculated Measures that make up
711     * each Calculated Measure. Thus, one ends up iterating across all
712     * Calculated and non-Calculated Measures that are explicitly
713     * mentioned in the query as well as all Calculated and non-Calculated
714     * Measures that are used to define the Calculated Measures in
715     * the query. Why all of these? because this represents the total
716     * scope of possible Measures that might yield a non-null value
717     * for the List-Members and that is what we what to find. It might
718     * be a super set, but thats ok; we just do not want to miss anything.
719     *
720     * <p>For other Members, the default Member is used, but for Measures one
721     * should look for that data for all Measures associated with the query, not
722     * just one Measure. For a dense dataset this may not be a problem or even
723     * apparent, but for a sparse dataset, the first Measure may, in fact, have
724     * not data but other Measures associated with the query might.
725     * Hence, the solution here is to identify all Measures associated with the
726     * query and then for each Member of the list, determine if there is any
727     * data iterating across all Measures until non-null data is found or the
728     * end of the Measures is reached.
729     *
730     * <p>This is a non-optimistic implementation. This means that an
731     * element of the input parameter List is only not included in the
732     * returned result List if for no combination of Measures, non-All
733     * Members (for Hierarchies that have no All Members) and evaluator
734     * default Members did the element evaluate to non-null.
735     *
736     * @param evaluator Evaluator
737     *
738     * @param list      List of members or tuples
739     *
740     * @param call      Calling ResolvedFunCall used to determine what Measures
741     *                  to use
742     *
743     * @return List of elements from the input parameter list that have
744     * evaluated to non-null.
745     */
746    protected TupleList nonEmptyList(
747        Evaluator evaluator,
748        TupleList list,
749        ResolvedFunCall call)
750    {
751        if (list.isEmpty()) {
752            return list;
753        }
754
755        TupleList result =
756            TupleCollections.createList(
757                list.getArity(), (list.size() + 2) >> 1);
758
759        // Get all of the Measures
760        final Query query = evaluator.getQuery();
761
762        final String measureSetKey = "MEASURE_SET-" + ctag;
763        Set<Member> measureSet =
764            Util.cast((Set) query.getEvalCache(measureSetKey));
765        // If not in query cache, then create and place into cache.
766        // This information is used for each iteration so it makes
767        // sense to create and cache it.
768        if (measureSet == null) {
769            measureSet = new HashSet<Member>();
770            Set<Member> queryMeasureSet = query.getMeasuresMembers();
771            MeasureVisitor visitor = new MeasureVisitor(measureSet, call);
772            for (Member m : queryMeasureSet) {
773                if (m.isCalculated()) {
774                    Exp exp = m.getExpression();
775                    exp.accept(visitor);
776                } else {
777                    measureSet.add(m);
778                }
779            }
780
781            Formula[] formula = query.getFormulas();
782            if (formula != null) {
783                for (Formula f : formula) {
784                    f.accept(visitor);
785                }
786            }
787
788            query.putEvalCache(measureSetKey, measureSet);
789        }
790
791        final String allMemberListKey = "ALL_MEMBER_LIST-" + ctag;
792        List<Member> allMemberList =
793            Util.cast((List) query.getEvalCache(allMemberListKey));
794
795        final String nonAllMembersKey = "NON_ALL_MEMBERS-" + ctag;
796        Member[][] nonAllMembers =
797            (Member[][]) query.getEvalCache(nonAllMembersKey);
798        if (nonAllMembers == null) {
799            //
800            // Get all of the All Members and those Hierarchies that
801            // do not have All Members.
802            //
803            Member[] evalMembers = evaluator.getMembers().clone();
804
805            List<Member> listMembers = list.get(0);
806
807            // Remove listMembers from evalMembers and independentSlicerMembers
808            for (Member lm : listMembers) {
809                Hierarchy h = lm.getHierarchy();
810                for (int i = 0; i < evalMembers.length; i++) {
811                    Member em = evalMembers[i];
812                    if ((em != null) && h.equals(em.getHierarchy())) {
813                        evalMembers[i] = null;
814                    }
815                }
816            }
817
818            List<Member> slicerMembers = null;
819            if (evaluator instanceof RolapEvaluator) {
820                RolapEvaluator rev = (RolapEvaluator) evaluator;
821                slicerMembers = rev.getSlicerMembers();
822            }
823            // Iterate the list of slicer members, grouping them by hierarchy
824            Map<Hierarchy, Set<Member>> mapOfSlicerMembers =
825                new HashMap<Hierarchy, Set<Member>>();
826            if (slicerMembers != null) {
827                for (Member slicerMember : slicerMembers) {
828                    Hierarchy hierarchy = slicerMember.getHierarchy();
829                    if (!mapOfSlicerMembers.containsKey(hierarchy)) {
830                        mapOfSlicerMembers.put(
831                            hierarchy,
832                            new HashSet<Member>());
833                    }
834                    mapOfSlicerMembers.get(hierarchy).add(slicerMember);
835                }
836            }
837
838            // Now we have the non-List-Members, but some of them may not be
839            // All Members (default Member need not be the All Member) and
840            // for some Hierarchies there may not be an All Member.
841            // So we create an array of Objects some elements of which are
842            // All Members and others elements will be an array of all top-level
843            // Members when there is not an All Member.
844            SchemaReader schemaReader = evaluator.getSchemaReader();
845            allMemberList = new ArrayList<Member>();
846            List<Member[]> nonAllMemberList = new ArrayList<Member[]>();
847
848            Member em;
849            boolean isSlicerMember;
850            for (Member evalMember : evalMembers) {
851                em = evalMember;
852
853                isSlicerMember =
854                    slicerMembers != null
855                        && slicerMembers.contains(em);
856
857                if (em == null) {
858                    // Above we might have removed some by setting them
859                    // to null. These are the CrossJoin axes.
860                    continue;
861                }
862                if (em.isMeasure()) {
863                    continue;
864                }
865
866                //
867                // The unconstrained members need to be replaced by the "All"
868                // member based on its usage and property. This is currently
869                // also the behavior of native cross join evaluation. See
870                // SqlConstraintUtils.addContextConstraint()
871                //
872                // on slicer? | calculated? | replace with All?
873                // -----------------------------------------------
874                //     Y      |      Y      |      Y always
875                //     Y      |      N      |      N
876                //     N      |      Y      |      N
877                //     N      |      N      |      Y if not "All"
878                // -----------------------------------------------
879                //
880                if ((isSlicerMember && !em.isCalculated())
881                    || (!isSlicerMember && em.isCalculated()))
882                {
883                    // If the slicer contains multiple members from this one's
884                    // hierarchy, add them to nonAllMemberList
885                    if (isSlicerMember) {
886                        Set<Member> hierarchySlicerMembers =
887                            mapOfSlicerMembers.get(em.getHierarchy());
888                        if (hierarchySlicerMembers.size() > 1) {
889                            nonAllMemberList.add(
890                                hierarchySlicerMembers.toArray(
891                                    new Member[hierarchySlicerMembers.size()]));
892                        }
893                    }
894                    continue;
895                }
896
897                // If the member is not the All member;
898                // or if it is a slicer member,
899                // replace with the "all" member.
900                if (isSlicerMember || !em.isAll()) {
901                    Hierarchy h = em.getHierarchy();
902                    final List<Member> rootMemberList =
903                        schemaReader.getHierarchyRootMembers(h);
904                    if (h.hasAll()) {
905                        // The Hierarchy has an All member
906                        boolean found = false;
907                        for (Member m : rootMemberList) {
908                            if (m.isAll()) {
909                                allMemberList.add(m);
910                                found = true;
911                                break;
912                            }
913                        }
914                        if (!found) {
915                            System.out.println(
916                                "CrossJoinFunDef.nonEmptyListNEW: ERROR");
917                        }
918                    } else {
919                        // The Hierarchy does NOT have an All member
920                        Member[] rootMembers =
921                            rootMemberList.toArray(
922                                new Member[rootMemberList.size()]);
923                        nonAllMemberList.add(rootMembers);
924                    }
925                }
926            }
927            nonAllMembers =
928                nonAllMemberList.toArray(
929                    new Member[nonAllMemberList.size()][]);
930
931            query.putEvalCache(allMemberListKey, allMemberList);
932            query.putEvalCache(nonAllMembersKey, nonAllMembers);
933        }
934
935        //
936        // Determine if there is any data.
937        //
938        // Put all of the All Members into Evaluator
939        final int savepoint = evaluator.savepoint();
940        try {
941            evaluator.setContext(allMemberList);
942            // Iterate over elements of the input list. If for any
943            // combination of
944            // Measure and non-All Members evaluation is non-null, then
945            // add it to the result List.
946            final TupleCursor cursor = list.tupleCursor();
947            while (cursor.forward()) {
948                cursor.setContext(evaluator);
949                if (checkData(
950                        nonAllMembers,
951                        nonAllMembers.length - 1,
952                        measureSet,
953                        evaluator))
954                {
955                    result.addCurrent(cursor);
956                }
957            }
958            return result;
959        } finally {
960            evaluator.restore(savepoint);
961        }
962    }
963
964    /**
965     * Return <code>true</code> if for some combination of Members
966     * from the nonAllMembers array of Member arrays and Measures from
967     * the Set of Measures evaluate to a non-null value. Even if a
968     * particular combination is non-null, all combinations are tested
969     * just to make sure that the data is loaded.
970     *
971     * @param nonAllMembers array of Member arrays of top-level Members
972     * for Hierarchies that have no All Member.
973     * @param cnt which Member array is to be processed.
974     * @param measureSet Set of all that should be tested against.
975     * @param evaluator the Evaluator.
976     * @return True if at least one combination evaluated to non-null.
977     */
978    private static boolean checkData(
979        Member[][] nonAllMembers,
980        int cnt,
981        Set<Member> measureSet,
982        Evaluator evaluator)
983    {
984        if (cnt < 0) {
985            // no measures found, use standard algorithm
986            if (measureSet.isEmpty()) {
987                Object value = evaluator.evaluateCurrent();
988                if (value != null
989                    && !(value instanceof Throwable))
990                {
991                    return true;
992                }
993            } else {
994                // Here we evaluate across all measures just to
995                // make sure that the data is all loaded
996                boolean found = false;
997                for (Member measure : measureSet) {
998                    evaluator.setContext(measure);
999                    Object value = evaluator.evaluateCurrent();
1000                    if (value != null
1001                        && !(value instanceof Throwable))
1002                    {
1003                        found = true;
1004                    }
1005                }
1006                return found;
1007            }
1008        } else {
1009            boolean found = false;
1010            for (Member m : nonAllMembers[cnt]) {
1011                evaluator.setContext(m);
1012                if (checkData(nonAllMembers, cnt - 1, measureSet, evaluator)) {
1013                    found = true;
1014                }
1015            }
1016            return found;
1017        }
1018        return false;
1019    }
1020
1021    private static class StarCrossJoinResolver extends MultiResolver {
1022        public StarCrossJoinResolver() {
1023            super(
1024                "*",
1025                "<Set1> * <Set2>",
1026                "Returns the cross product of two sets.",
1027                new String[]{"ixxx", "ixmx", "ixxm", "ixmm"});
1028        }
1029
1030        public FunDef resolve(
1031            Exp[] args,
1032            Validator validator,
1033            List<Conversion> conversions)
1034        {
1035            // This function only applies in contexts which require a set.
1036            // Elsewhere, "*" is the multiplication operator.
1037            // This means that [Measures].[Unit Sales] * [Gender].[M] is
1038            // well-defined.
1039            if (validator.requiresExpression()) {
1040                return null;
1041            }
1042            return super.resolve(args, validator, conversions);
1043        }
1044
1045        protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
1046            return new CrossJoinFunDef(dummyFunDef);
1047        }
1048    }
1049}
1050
1051// End CrossJoinFunDef.java