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 TONBELLER AG
008// Copyright (C) 2006-2011 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.rolap.sql;
012
013import mondrian.calc.*;
014import mondrian.mdx.*;
015import mondrian.olap.*;
016import mondrian.olap.Role.RollupPolicy;
017import mondrian.olap.fun.*;
018import mondrian.olap.type.HierarchyType;
019import mondrian.olap.type.Type;
020import mondrian.rolap.*;
021
022import org.apache.log4j.Logger;
023
024import java.util.*;
025
026/**
027 * Creates CrossJoinArgs for use in constraining SQL queries.
028 *
029 * @author kwalker
030 * @since Dec 15, 2009
031 */
032public class CrossJoinArgFactory {
033    protected static final Logger LOGGER =
034        Logger.getLogger(CrossJoinArgFactory.class);
035    private boolean restrictMemberTypes;
036
037    public CrossJoinArgFactory(boolean restrictMemberTypes) {
038        this.restrictMemberTypes = restrictMemberTypes;
039    }
040
041    public Set<CrossJoinArg> buildConstraintFromAllAxes(
042        final RolapEvaluator evaluator)
043    {
044        Set<CrossJoinArg> joinArgs =
045            new LinkedHashSet<CrossJoinArg>();
046        for (QueryAxis ax : evaluator.getQuery().getAxes()) {
047            List<CrossJoinArg[]> axesArgs =
048                checkCrossJoinArg(evaluator, ax.getSet(), true);
049            if (axesArgs != null) {
050                for (CrossJoinArg[] axesArg : axesArgs) {
051                    joinArgs.addAll(Arrays.asList(axesArg));
052                }
053            }
054        }
055        return joinArgs;
056    }
057
058    /**
059     * Scans for memberChildren, levelMembers, memberDescendants, crossJoin.
060     */
061    public List<CrossJoinArg[]> checkCrossJoinArg(
062        RolapEvaluator evaluator,
063        Exp exp)
064    {
065        return checkCrossJoinArg(evaluator, exp, false);
066    }
067
068    /**
069     * Checks whether an expression can be natively evaluated. The following
070     * expressions can be natively evaluated:
071     * <p/>
072     * <ul>
073     * <li>member.Children
074     * <li>level.members
075     * <li>descendents of a member
076     * <li>member list
077     * <li>filter on a dimension
078     * </ul>
079     *
080     * @param evaluator Evaluator
081     * @param exp       Expresssion
082     * @return List of CrossJoinArg arrays. The first array represent the
083     *         CJ CrossJoinArg and the second array represent the additional
084     *         constraints.
085     */
086    List<CrossJoinArg[]> checkCrossJoinArg(
087        RolapEvaluator evaluator,
088        Exp exp,
089        final boolean returnAny)
090    {
091        if (exp instanceof NamedSetExpr) {
092            NamedSet namedSet = ((NamedSetExpr) exp).getNamedSet();
093            exp = namedSet.getExp();
094        }
095        if (!(exp instanceof ResolvedFunCall)) {
096            return null;
097        }
098        final ResolvedFunCall funCall = (ResolvedFunCall) exp;
099        FunDef fun = funCall.getFunDef();
100        Exp[] args = funCall.getArgs();
101
102        final Role role = evaluator.getSchemaReader().getRole();
103        CrossJoinArg[] cjArgs;
104
105        cjArgs = checkMemberChildren(role, fun, args);
106        if (cjArgs != null) {
107            return Collections.singletonList(cjArgs);
108        }
109        cjArgs = checkLevelMembers(role, fun, args);
110        if (cjArgs != null) {
111            return Collections.singletonList(cjArgs);
112        }
113        cjArgs = checkDescendants(role, fun, args);
114        if (cjArgs != null) {
115            return Collections.singletonList(cjArgs);
116        }
117        final boolean exclude = false;
118        cjArgs = checkEnumeration(evaluator, fun, args, exclude);
119        if (cjArgs != null) {
120            return Collections.singletonList(cjArgs);
121        }
122
123        if (returnAny) {
124            cjArgs = checkConstrainedMeasures(evaluator, fun, args);
125            if (cjArgs != null) {
126                return Collections.singletonList(cjArgs);
127            }
128        }
129
130        List<CrossJoinArg[]> allArgs =
131            checkDimensionFilter(evaluator, fun, args);
132        if (allArgs != null) {
133            return allArgs;
134        }
135        // strip off redundant set braces, for example
136        // { Gender.Gender.members }, or {{{ Gender.M }}}
137        if ("{}".equalsIgnoreCase(fun.getName()) && args.length == 1) {
138            return checkCrossJoinArg(evaluator, args[0], returnAny);
139        }
140        if ("NativizeSet".equalsIgnoreCase(fun.getName()) && args.length == 1) {
141            return checkCrossJoinArg(evaluator, args[0], returnAny);
142        }
143        return checkCrossJoin(evaluator, fun, args, returnAny);
144    }
145
146    private CrossJoinArg[] checkConstrainedMeasures(
147        RolapEvaluator evaluator, FunDef fun, Exp[] args)
148    {
149            if (isSetOfConstrainedMeasures(fun, args)) {
150            HashMap<Dimension, List<RolapMember>> memberLists =
151                new LinkedHashMap<Dimension, List<RolapMember>>();
152            for (Exp arg : args) {
153                addConstrainingMembersToMap(arg, memberLists);
154            }
155            return memberListCrossJoinArgArray(memberLists, args, evaluator);
156        }
157        return null;
158    }
159
160    private boolean isSetOfConstrainedMeasures(FunDef fun, Exp[] args) {
161        return fun.getName().equals("{}") && allArgsConstrainedMeasure(args);
162    }
163
164    private boolean allArgsConstrainedMeasure(Exp[] args) {
165        for (Exp arg : args) {
166            if (!isConstrainedMeasure(arg)) {
167                return false;
168            }
169        }
170        return true;
171    }
172
173    private boolean isConstrainedMeasure(Exp arg) {
174        if (!(arg instanceof MemberExpr
175            && ((MemberExpr) arg).getMember().isMeasure()))
176        {
177            if (arg instanceof ResolvedFunCall) {
178                ResolvedFunCall call = (ResolvedFunCall) arg;
179                if (call.getFunDef() instanceof SetFunDef
180                    || call.getFunDef() instanceof ParenthesesFunDef)
181                {
182                    return allArgsConstrainedMeasure(call.getArgs());
183                }
184            }
185            return false;
186        }
187        Member member = ((MemberExpr) arg).getMember();
188        if (member instanceof RolapCalculatedMember) {
189            Exp calcExp =
190                ((RolapCalculatedMember) member).getFormula().getExpression();
191            return ((calcExp instanceof ResolvedFunCall
192                && ((ResolvedFunCall) calcExp).getFunDef()
193                instanceof TupleFunDef))
194                || calcExp instanceof Literal;
195        }
196        return false;
197    }
198
199    private void addConstrainingMembersToMap(
200        Exp arg, Map<Dimension, List<RolapMember>> memberLists)
201    {
202        if (arg instanceof ResolvedFunCall) {
203            ResolvedFunCall call = (ResolvedFunCall) arg;
204            for (Exp callArg : call.getArgs()) {
205                addConstrainingMembersToMap(callArg, memberLists);
206            }
207        }
208        Exp[] tupleArgs = getCalculatedTupleArgs(arg);
209        for (Exp tupleArg : tupleArgs) {
210            Dimension dimension = tupleArg.getType().getDimension();
211            if (!dimension.isMeasures()) {
212                List<RolapMember> members;
213                if (memberLists.containsKey(dimension)) {
214                    members = memberLists.get(dimension);
215                } else {
216                    members = new ArrayList<RolapMember>();
217                }
218                members.add((RolapMember) ((MemberExpr) tupleArg).getMember());
219                memberLists.put(dimension, members);
220            } else if (isConstrainedMeasure(tupleArg)) {
221                addConstrainingMembersToMap(tupleArg, memberLists);
222            }
223        }
224    }
225
226    private Exp[] getCalculatedTupleArgs(Exp arg) {
227        if (arg instanceof MemberExpr) {
228            Member member = ((MemberExpr) arg).getMember();
229            if (member instanceof RolapCalculatedMember) {
230                Exp formulaExp =
231                    ((RolapCalculatedMember) member)
232                        .getFormula().getExpression();
233                if (formulaExp instanceof ResolvedFunCall) {
234                    return ((ResolvedFunCall) formulaExp).getArgs();
235                }
236            }
237        }
238        return new Exp[0];
239    }
240
241    private CrossJoinArg[] memberListCrossJoinArgArray(
242        Map<Dimension, List<RolapMember>> memberLists,
243        Exp[] args,
244        RolapEvaluator evaluator)
245    {
246        List<CrossJoinArg> argList = new ArrayList<CrossJoinArg>();
247        for (List<RolapMember> memberList : memberLists.values()) {
248            if (memberList.size() == countNonLiteralMeasures(args)) {
249                //when the memberList and args list have the same length
250                //it means there must have been a constraint on each measure
251                //for this dimension.
252                final CrossJoinArg cjArg =
253                    MemberListCrossJoinArg.create(
254                        evaluator,
255                        removeDuplicates(memberList),
256                        restrictMemberTypes(), false);
257                if (cjArg != null) {
258                    argList.add(cjArg);
259                }
260            }
261        }
262        if (argList.size() > 0) {
263            return argList.toArray(new CrossJoinArg[argList.size()]);
264        }
265        return null;
266    }
267
268    private List<RolapMember> removeDuplicates(List<RolapMember> list)
269    {
270        Set<RolapMember> set = new HashSet<RolapMember>();
271        List<RolapMember> uniqueList = new ArrayList<RolapMember>();
272        for (RolapMember element : list) {
273            if (set.add(element)) {
274                uniqueList.add(element);
275            }
276        }
277        return uniqueList;
278    }
279
280    private int countNonLiteralMeasures(Exp[] length) {
281        int count = 0;
282        for (Exp exp : length) {
283            if (exp instanceof MemberExpr) {
284                Exp calcExp = ((MemberExpr) exp).getMember().getExpression();
285                if (!(calcExp instanceof Literal)) {
286                    count++;
287                }
288            } else if (exp instanceof ResolvedFunCall) {
289                count +=
290                    countNonLiteralMeasures(((ResolvedFunCall) exp).getArgs());
291            }
292        }
293        return count;
294    }
295
296    /**
297     * Checks for <code>CrossJoin(&lt;set1&gt;, &lt;set2&gt;)</code>, where
298     * set1 and set2 are one of
299     * <code>member.children</code>, <code>level.members</code> or
300     * <code>member.descendants</code>.
301     *
302     * @param evaluator Evaluator to use if inputs are to be evaluated
303     * @param fun       The function, either "CrossJoin" or "NonEmptyCrossJoin"
304     * @param args      Inputs to the CrossJoin
305     * @param returnAny indicates we should return any valid crossjoin args
306     * @return array of CrossJoinArg representing the inputs
307     */
308    public List<CrossJoinArg[]> checkCrossJoin(
309        RolapEvaluator evaluator,
310        FunDef fun,
311        Exp[] args,
312        final boolean returnAny)
313    {
314        // is this "CrossJoin([A].children, [B].children)"
315        if (!"Crossjoin".equalsIgnoreCase(fun.getName())
316            && !"NonEmptyCrossJoin".equalsIgnoreCase(fun.getName()))
317        {
318            return null;
319        }
320        if (args.length != 2) {
321            return null;
322        }
323        // Check if the arguments can be natively evaluated.
324        // If not, try evaluating this argument and turning the result into
325        // MemberListCrossJoinArg.
326        List<CrossJoinArg[]> allArgsOneInput;
327        // An array(size 2) of arrays(size arbitary). Each outer array represent
328        // native inputs fro one input.
329        CrossJoinArg[][] cjArgsBothInputs =
330            new CrossJoinArg[2][];
331        CrossJoinArg[][] predicateArgsBothInputs =
332            new CrossJoinArg[2][];
333
334        for (int i = 0; i < 2; i++) {
335            allArgsOneInput = checkCrossJoinArg(evaluator, args[i], returnAny);
336
337            if (allArgsOneInput == null
338                || allArgsOneInput.isEmpty()
339                || allArgsOneInput.get(0) == null)
340            {
341                cjArgsBothInputs[i] = expandNonNative(evaluator, args[i]);
342            } else {
343                // Collect CJ CrossJoinArg
344                cjArgsBothInputs[i] = allArgsOneInput.get(0);
345            }
346            if (returnAny) {
347                continue;
348            }
349            if (cjArgsBothInputs[i] == null) {
350                return null;
351            }
352
353            // Collect Predicate CrossJoinArg if it exists.
354            predicateArgsBothInputs[i] = null;
355            if (allArgsOneInput != null && allArgsOneInput.size() == 2) {
356                predicateArgsBothInputs[i] = allArgsOneInput.get(1);
357            }
358        }
359
360        List<CrossJoinArg[]> allArgsBothInputs =
361            new ArrayList<CrossJoinArg[]>();
362        // Now combine the cjArgs from both sides
363        CrossJoinArg[] combinedCJArgs =
364            Util.appendArrays(
365                cjArgsBothInputs[0] == null
366                    ? CrossJoinArg.EMPTY_ARRAY
367                    : cjArgsBothInputs[0],
368                cjArgsBothInputs[1] == null
369                    ? CrossJoinArg.EMPTY_ARRAY
370                    : cjArgsBothInputs[1]);
371        allArgsBothInputs.add(combinedCJArgs);
372
373        CrossJoinArg[] combinedPredicateArgs =
374            Util.appendArrays(
375                predicateArgsBothInputs[0] == null
376                    ? CrossJoinArg.EMPTY_ARRAY
377                    : predicateArgsBothInputs[0],
378                predicateArgsBothInputs[1] == null
379                    ? CrossJoinArg.EMPTY_ARRAY
380                    : predicateArgsBothInputs[1]);
381        if (combinedPredicateArgs.length > 0) {
382            allArgsBothInputs.add(combinedPredicateArgs);
383        }
384
385        return allArgsBothInputs;
386    }
387
388    /**
389     * Checks for a set constructor, <code>{member1, member2,
390     * &#46;&#46;&#46;}</code> that does not contain calculated members.
391     *
392     * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the enumeration,
393     *         or null if <code>fun</code> represents something else.
394     */
395    private CrossJoinArg[] checkEnumeration(
396        RolapEvaluator evaluator,
397        FunDef fun,
398        Exp[] args,
399        boolean exclude)
400    {
401        // Return null if not the expected function name or input size.
402        if (fun == null) {
403            if (args.length != 1) {
404                return null;
405            }
406        } else {
407            if (!"{}".equalsIgnoreCase(fun.getName())
408                || !isArgSizeSupported(evaluator, args.length))
409            {
410                return null;
411            }
412        }
413
414        List<RolapMember> memberList = new ArrayList<RolapMember>();
415        for (Exp arg : args) {
416            if (!(arg instanceof MemberExpr)) {
417                return null;
418            }
419            final Member member = ((MemberExpr) arg).getMember();
420            if (member.isCalculated()
421                && !member.isParentChildLeaf())
422            {
423                // also returns null if any member is calculated
424                return null;
425            }
426            memberList.add((RolapMember) member);
427        }
428
429        final CrossJoinArg cjArg =
430            MemberListCrossJoinArg.create(
431                evaluator, memberList, restrictMemberTypes(), exclude);
432        if (cjArg == null) {
433            return null;
434        }
435        return new CrossJoinArg[]{cjArg};
436    }
437
438    private boolean restrictMemberTypes() {
439        return restrictMemberTypes;
440    }
441
442    /**
443     * Checks for <code>&lt;Member&gt;.Children</code>.
444     *
445     * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the member.children
446     *         function, or null if <code>fun</code> represents something else.
447     */
448    private CrossJoinArg[] checkMemberChildren(
449        Role role,
450        FunDef fun,
451        Exp[] args)
452    {
453        if (!"Children".equalsIgnoreCase(fun.getName())) {
454            return null;
455        }
456        if (args.length != 1) {
457            return null;
458        }
459
460        // Note: <Dimension>.Children is not recognized as a native expression.
461        if (!(args[0] instanceof MemberExpr)) {
462            return null;
463        }
464        RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember();
465        if (member.isCalculated()) {
466            return null;
467        }
468        RolapLevel level = member.getLevel();
469        level = (RolapLevel) level.getChildLevel();
470        if (level == null || !level.isSimple()) {
471            // no child level
472            return null;
473        }
474        // Children of a member in an access-controlled hierarchy cannot be
475        // converted to SQL when RollupPolicy=FULL. (We could be smarter; we
476        // don't currently notice when we don't look below the rolled up level
477        // therefore no access-control is needed.
478        final Access access = role.getAccess(level.getHierarchy());
479        switch (access) {
480        case ALL:
481            break;
482        case CUSTOM:
483            final RollupPolicy rollupPolicy =
484                role.getAccessDetails(level.getHierarchy()).getRollupPolicy();
485            if (rollupPolicy == RollupPolicy.FULL) {
486                return null;
487            }
488        break;
489        default:
490            return null;
491        }
492        return new CrossJoinArg[]{
493            new DescendantsCrossJoinArg(level, member)
494        };
495    }
496
497    /**
498     * Checks for <code>&lt;Level&gt;.Members</code>.
499     *
500     * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the Level.members
501     *         function, or null if <code>fun</code> represents something else.
502     */
503    private CrossJoinArg[] checkLevelMembers(
504        Role role,
505        FunDef fun,
506        Exp[] args)
507    {
508        if (!"Members".equalsIgnoreCase(fun.getName())) {
509            return null;
510        }
511        if (args.length != 1) {
512            return null;
513        }
514        if (!(args[0] instanceof LevelExpr)) {
515            return null;
516        }
517        RolapLevel level = (RolapLevel) ((LevelExpr) args[0]).getLevel();
518        if (!level.isSimple()) {
519            return null;
520        }
521        // Members of a level in an access-controlled hierarchy cannot be
522        // converted to SQL when RollupPolicy=FULL. (We could be smarter; we
523        // don't currently notice when we don't look below the rolled up level
524        // therefore no access-control is needed.
525        final Access access = role.getAccess(level.getHierarchy());
526        switch (access) {
527        case ALL:
528            break;
529        case CUSTOM:
530            final RollupPolicy rollupPolicy =
531                role.getAccessDetails(level.getHierarchy()).getRollupPolicy();
532            if (rollupPolicy == RollupPolicy.FULL) {
533                return null;
534            }
535        break;
536        default:
537            return null;
538        }
539        return new CrossJoinArg[]{
540            new DescendantsCrossJoinArg(level, null)
541        };
542    }
543
544
545    private static boolean isArgSizeSupported(
546        RolapEvaluator evaluator,
547        int argSize)
548    {
549        boolean argSizeNotSupported = false;
550
551        // Note: arg size 0 is accepted as valid CJ argument
552        // This is used to push down the "1 = 0" predicate
553        // into the emerging CJ so that the entire CJ can
554        // be natively evaluated.
555
556        // First check that the member list will not result in a predicate
557        // longer than the underlying DB could support.
558        if (argSize > MondrianProperties.instance().MaxConstraints.get()) {
559            argSizeNotSupported = true;
560        }
561
562        return !argSizeNotSupported;
563    }
564
565    /**
566     * Checks for Descendants(&lt;member&gt;, &lt;Level&gt;)
567     *
568     * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the Descendants
569     *         function, or null if <code>fun</code> represents something else.
570     */
571    private CrossJoinArg[] checkDescendants(
572        Role role,
573        FunDef fun,
574        Exp[] args)
575    {
576        if (!"Descendants".equalsIgnoreCase(fun.getName())) {
577            return null;
578        }
579        if (args.length != 2) {
580            return null;
581        }
582        if (!(args[0] instanceof MemberExpr)) {
583            return null;
584        }
585        RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember();
586        if (member.isCalculated()) {
587            return null;
588        }
589        RolapLevel level = null;
590        if ((args[1] instanceof LevelExpr)) {
591            level = (RolapLevel) ((LevelExpr) args[1]).getLevel();
592        } else if (args[1] instanceof Literal) {
593            RolapLevel[] levels = (RolapLevel[])
594                member.getHierarchy().getLevels();
595            int currentDepth = member.getDepth();
596            Literal descendantsDepth = (Literal) args[1];
597            int newDepth = currentDepth + descendantsDepth.getIntValue();
598            if (newDepth < levels.length) {
599                level = levels[newDepth];
600            }
601        } else {
602            return null;
603        }
604
605        if (!level.isSimple()) {
606            return null;
607        }
608        // Descendants of a member in an access-controlled hierarchy cannot be
609        // converted to SQL. (We could be smarter; we don't currently notice
610        // when the member is in a part of the hierarchy that is not
611        // access-controlled.)
612        final Access access = role.getAccess(level.getHierarchy());
613        switch (access) {
614        case ALL:
615            break;
616        default:
617            return null;
618        }
619        return new CrossJoinArg[]{
620            new DescendantsCrossJoinArg(level, member)
621        };
622    }
623
624    /**
625     * Check if a dimension filter can be natively evaluated.
626     * Currently, these types of filters can be natively evaluated:
627     * Filter(Set, Qualified Predicate)
628     * where Qualified Predicate is either
629     * CurrentMember reference IN {m1, m2},
630     * CurrentMember reference Is m1,
631     * negation(NOT) of qualified predicate
632     * conjuction(AND) of qualified predicates
633     * and where
634     * currentmember reference is either a member or
635     * ancester of a member from the context,
636     *
637     * @param evaluator  Evaluator
638     * @param fun        Filter function
639     * @param filterArgs inputs to the Filter function
640     * @return a list of CrossJoinArg arrays. The first array is the CrossJoin
641     *         dimensions. The second array, if any, contains additional
642     *         constraints on the dimensions. If either the list or the first
643     *         array is null, then native cross join is not feasible.
644     */
645    private List<CrossJoinArg[]> checkDimensionFilter(
646        RolapEvaluator evaluator,
647        FunDef fun,
648        Exp[] filterArgs)
649    {
650        if (!MondrianProperties.instance().EnableNativeFilter.get()) {
651            return null;
652        }
653
654        // Return null if not the expected funciton name or input size.
655        if (!"Filter".equalsIgnoreCase(fun.getName())
656            || filterArgs.length != 2)
657        {
658            return null;
659        }
660
661        // Now check filterArg[0] can be natively evaluated.
662        // checkCrossJoin returns a list of CrossJoinArg arrays.
663        // The first array is the CrossJoin dimensions
664        // The second array, if any, contains additional constraints on the
665        // dimensions. If either the list or the first array is null, then
666        // native cross join is not feasible.
667        List<CrossJoinArg[]> allArgs =
668            checkCrossJoinArg(evaluator, filterArgs[0]);
669
670        if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) {
671            return null;
672        }
673
674        final CrossJoinArg[] cjArgs = allArgs.get(0);
675        if (cjArgs == null) {
676            return null;
677        }
678
679        final CrossJoinArg[] previousPredicateArgs;
680        if (allArgs.size() == 2) {
681            previousPredicateArgs = allArgs.get(1);
682        } else {
683            previousPredicateArgs = null;
684        }
685
686        // True if the Filter wants to exclude member(s)
687        final boolean exclude = false;
688
689        // Check that filterArgs[1] is a qualified predicate
690        // Composites such as AND/OR are not supported at this time
691        CrossJoinArg[] currentPredicateArgs;
692        if (filterArgs[1] instanceof ResolvedFunCall) {
693            ResolvedFunCall predicateCall = (ResolvedFunCall) filterArgs[1];
694
695            currentPredicateArgs =
696                checkFilterPredicate(evaluator, predicateCall, exclude);
697        } else {
698            currentPredicateArgs = null;
699        }
700
701        if (currentPredicateArgs == null) {
702            return null;
703        }
704
705        // cjArgs remain the same but now there is more predicateArgs
706        // Combine the previous predicate args with the current predicate args.
707        LOGGER.debug("using native dimension filter");
708        CrossJoinArg[] combinedPredicateArgs =
709            currentPredicateArgs;
710
711        if (previousPredicateArgs != null) {
712            combinedPredicateArgs =
713                Util.appendArrays(previousPredicateArgs, currentPredicateArgs);
714        }
715
716        // CJ args do not change.
717        // Predicate args will grow if filter is native.
718        return Arrays.asList(cjArgs, combinedPredicateArgs);
719    }
720
721    /**
722     * Checks whether the filter predicate can be turned into native SQL.
723     * See comment for checkDimensionFilter for the types of predicates
724     * suported.
725     *
726     * @param evaluator     Evaluator
727     * @param predicateCall Call to predicate function (ANd, NOT or parentheses)
728     * @param exclude       Whether to exclude tuples that match the predicate
729     * @return if filter predicate can be natively evaluated, the CrossJoinArg
730     *         array representing the predicate; otherwise, null.
731     */
732    private CrossJoinArg[] checkFilterPredicate(
733        RolapEvaluator evaluator,
734        ResolvedFunCall predicateCall,
735        boolean exclude)
736    {
737        CrossJoinArg[] predicateCJArgs = null;
738        if (predicateCall.getFunName().equals("()")) {
739            Exp actualPredicateCall = predicateCall.getArg(0);
740            if (actualPredicateCall instanceof ResolvedFunCall) {
741                return checkFilterPredicate(
742                    evaluator, (ResolvedFunCall) actualPredicateCall, exclude);
743            } else {
744                return null;
745            }
746        }
747
748        if (predicateCall.getFunName().equals("NOT")
749            && predicateCall.getArg(0) instanceof ResolvedFunCall)
750        {
751            predicateCall = (ResolvedFunCall) predicateCall.getArg(0);
752            // Flip the exclude flag
753            exclude = !exclude;
754            return checkFilterPredicate(evaluator, predicateCall, exclude);
755        }
756
757        if (predicateCall.getFunName().equals("AND")) {
758            Exp andArg0 = predicateCall.getArg(0);
759            Exp andArg1 = predicateCall.getArg(1);
760
761            if (andArg0 instanceof ResolvedFunCall
762                && andArg1 instanceof ResolvedFunCall)
763            {
764                CrossJoinArg[] andCJArgs0;
765                CrossJoinArg[] andCJArgs1;
766                andCJArgs0 =
767                    checkFilterPredicate(
768                        evaluator, (ResolvedFunCall) andArg0, exclude);
769                if (andCJArgs0 != null) {
770                    andCJArgs1 =
771                        checkFilterPredicate(
772                            evaluator, (ResolvedFunCall) andArg1, exclude);
773                    if (andCJArgs1 != null) {
774                        predicateCJArgs =
775                            Util.appendArrays(andCJArgs0, andCJArgs1);
776                    }
777                }
778            }
779            // predicateCJArgs is either initialized or null
780            return predicateCJArgs;
781        }
782
783        // Now check the broken down predicate clause.
784        predicateCJArgs =
785            checkFilterPredicateInIs(evaluator, predicateCall, exclude);
786        return predicateCJArgs;
787    }
788
789    /**
790     * Check whether the predicate is an IN or IS predicate and can be
791     * natively evaluated.
792     *
793     * @param evaluator
794     * @param predicateCall
795     * @param exclude
796     * @return the array of CrossJoinArg containing the predicate.
797     */
798    private CrossJoinArg[] checkFilterPredicateInIs(
799        RolapEvaluator evaluator,
800        ResolvedFunCall predicateCall,
801        boolean exclude)
802    {
803        final boolean useIs;
804        if (predicateCall.getFunName().equals("IS")) {
805            useIs = true;
806        } else if (predicateCall.getFunName().equals("IN")) {
807            useIs = false;
808        } else {
809            // Neither IN nor IS
810            // This predicate can not be natively evaluated.
811            return null;
812        }
813
814        Exp[] predArgs = predicateCall.getArgs();
815        if (predArgs.length != 2) {
816            return null;
817        }
818
819        // Check that predArgs[0] is a ResolvedFuncCall while FunDef is:
820        //   DimensionCurrentMemberFunDef
821        //   HierarchyCurrentMemberFunDef
822        //   or Ancestor of those functions.
823        if (!(predArgs[0] instanceof ResolvedFunCall)) {
824            return null;
825        }
826
827        ResolvedFunCall predFirstArgCall = (ResolvedFunCall) predArgs[0];
828        if (predFirstArgCall.getFunDef().getName().equals("Ancestor")) {
829            Exp[] ancestorArgs = predFirstArgCall.getArgs();
830
831            if (!(ancestorArgs[0] instanceof ResolvedFunCall)) {
832                return null;
833            }
834
835            predFirstArgCall = (ResolvedFunCall) ancestorArgs[0];
836        }
837
838        // Now check that predFirstArgCall is a CurrentMember function that
839        // refers to the dimension being filtered
840        FunDef predFirstArgFun = predFirstArgCall.getFunDef();
841        if (!predFirstArgFun.getName().equals("CurrentMember")) {
842            return null;
843        }
844
845        Exp currentMemberArg = predFirstArgCall.getArg(0);
846        Type currentMemberArgType = currentMemberArg.getType();
847
848        // Input to CurremntMember should be either Dimension or Hierarchy type.
849        if (!(currentMemberArgType
850            instanceof mondrian.olap.type.DimensionType
851            || currentMemberArgType instanceof HierarchyType))
852        {
853            return null;
854        }
855
856        // It is not necessary to check currentMemberArg comes from the same
857        // dimension as one of the filterCJArgs, because query parser makes sure
858        // that currentMember always references dimensions in context.
859
860        // Check that predArgs[1] can be expressed as an MemberListCrossJoinArg.
861        Exp predSecondArg = predArgs[1];
862        Exp[] predSecondArgList;
863        FunDef predSecondArgFun;
864        CrossJoinArg[] predCJArgs;
865
866        if (useIs) {
867            // IS operator
868            if (!(predSecondArg instanceof MemberExpr)) {
869                return null;
870            }
871
872            // IS predicate only contains one member
873            // Make it into a list to be uniform with IN predicate.
874            predSecondArgFun = null;
875            predSecondArgList = new Exp[]{predSecondArg};
876        } else {
877            // IN operator
878            if (predSecondArg instanceof NamedSetExpr) {
879                NamedSet namedSet =
880                    ((NamedSetExpr) predSecondArg).getNamedSet();
881                predSecondArg = namedSet.getExp();
882            }
883
884            if (!(predSecondArg instanceof ResolvedFunCall)) {
885                return null;
886            }
887
888            ResolvedFunCall predSecondArgCall =
889                (ResolvedFunCall) predSecondArg;
890            predSecondArgFun = predSecondArgCall.getFunDef();
891            predSecondArgList = predSecondArgCall.getArgs();
892        }
893
894        predCJArgs =
895            checkEnumeration(
896                evaluator, predSecondArgFun, predSecondArgList, exclude);
897        return predCJArgs;
898    }
899
900    private CrossJoinArg[] expandNonNative(
901        RolapEvaluator evaluator,
902        Exp exp)
903    {
904        ExpCompiler compiler = evaluator.getQuery().createCompiler();
905        CrossJoinArg[] arg0 = null;
906        if (shouldExpandNonEmpty(exp)
907            && evaluator.getActiveNativeExpansions().add(exp))
908        {
909            ListCalc listCalc0 = compiler.compileList(exp);
910            final TupleList tupleList = listCalc0.evaluateList(evaluator);
911
912            // Prevent the case when the second argument size is too large
913            Util.checkCJResultLimit(tupleList.size());
914
915            if (tupleList.getArity() == 1) {
916                List<RolapMember> list0 =
917                    Util.cast(tupleList.slice(0));
918                CrossJoinArg arg =
919                    MemberListCrossJoinArg.create(
920                        evaluator, list0, restrictMemberTypes(), false);
921                if (arg != null) {
922                    arg0 = new CrossJoinArg[]{arg};
923                }
924            }
925            evaluator.getActiveNativeExpansions().remove(exp);
926        }
927        return arg0;
928    }
929
930    private boolean shouldExpandNonEmpty(Exp exp) {
931        return MondrianProperties.instance().ExpandNonNative.get()
932//               && !MondrianProperties.instance().EnableNativeCrossJoin.get()
933            || isCheapSet(exp);
934    }
935
936    private boolean isCheapSet(Exp exp) {
937        return isSet(exp) && allArgsCheapToExpand(exp);
938    }
939
940    private static final List<String> cheapFuns =
941        Arrays.asList("LastChild", "FirstChild", "Lag");
942
943    private boolean allArgsCheapToExpand(Exp exp) {
944        while (exp instanceof NamedSetExpr) {
945            exp = ((NamedSetExpr) exp).getNamedSet().getExp();
946        }
947        for (Exp arg : ((ResolvedFunCall) exp).getArgs()) {
948            if (arg instanceof ResolvedFunCall) {
949                if (!cheapFuns.contains(((ResolvedFunCall) arg).getFunName())) {
950                    return false;
951                }
952            } else if (!(arg instanceof MemberExpr)) {
953                return false;
954            }
955        }
956        return true;
957    }
958
959    private boolean isSet(Exp exp) {
960        return ((exp instanceof ResolvedFunCall)
961            && ((ResolvedFunCall) exp).getFunName().equals("{}"))
962            || (exp instanceof NamedSetExpr);
963    }
964}
965
966// End CrossJoinArgFactory.java