001    /*
002    // This software is subject to the terms of the Common Public License
003    // Agreement, available at the following URL:
004    // http://www.opensource.org/licenses/cpl.html.
005    // Copyright (C) 2004-2005 TONBELLER AG
006    // All Rights Reserved.
007    // You must accept the terms of that agreement to use this software.
008    */
009    package mondrian.rolap;
010    
011    import java.util.*;
012    
013    import mondrian.calc.*;
014    import mondrian.olap.*;
015    import mondrian.rolap.TupleReader.MemberBuilder;
016    import mondrian.rolap.cache.HardSmartCache;
017    import mondrian.rolap.cache.SmartCache;
018    import mondrian.rolap.cache.SoftSmartCache;
019    import mondrian.rolap.sql.MemberChildrenConstraint;
020    import mondrian.rolap.sql.SqlQuery;
021    import mondrian.rolap.sql.TupleConstraint;
022    import mondrian.mdx.*;
023    
024    import org.apache.log4j.Logger;
025    
026    import javax.sql.DataSource;
027    
028    /**
029     * Analyses set expressions and executes them in SQL if possible.
030     * Supports crossjoin, member.children, level.members and member.descendants -
031     * all in non empty mode, i.e. there is a join to the fact table.<p/>
032     *
033     * TODO: the order of the result is different from the order of the
034     * enumeration. Should sort.
035     *
036     * @author av
037     * @since Nov 12, 2005
038     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapNativeSet.java#44 $
039     */
040    public abstract class RolapNativeSet extends RolapNative {
041        protected static final Logger LOGGER = Logger.getLogger(RolapNativeSet.class);
042    
043        private SmartCache<Object, List<List<RolapMember>>> cache =
044            new SoftSmartCache<Object, List<List<RolapMember>>>();
045    
046        /**
047         * Returns whether certain member types(e.g. calculated members) should
048         * disable native SQL evaluation for expressions containing them.
049         *
050         * <p>
051         * If true, expressions containing calculated members will be evaluated by
052         * the interpreter, instead of using SQL.
053         *
054         * If false, calc members will be ignored and the computation will be done
055         * in SQL, returning more members than requested.
056         * </p>
057         */
058        protected abstract boolean restrictMemberTypes();
059    
060        /**
061         * Constraint for non empty {crossjoin, member.children,
062         * member.descendants, level.members}
063         */
064        protected static abstract class SetConstraint extends SqlContextConstraint {
065            CrossJoinArg[] args;
066    
067            SetConstraint(CrossJoinArg[] args, RolapEvaluator evaluator, boolean strict) {
068                super(evaluator, strict);
069                this.args = args;
070            }
071    
072            /**
073             * if there is a crossjoin, we need to join the fact table - even if the
074             * evalutaor context is empty.
075             */
076            protected boolean isJoinRequired() {
077                return args.length > 1 || super.isJoinRequired();
078            }
079    
080            public void addConstraint(SqlQuery sqlQuery, RolapCube baseCube) {
081                super.addConstraint(sqlQuery, baseCube);
082                for (CrossJoinArg arg : args) {
083                    // if the cross join argument has calculated members in its
084                    // enumerated set, ignore the constraint since we won't
085                    // produce that set through the native sql and instead
086                    // will simply enumerate through the members in the set
087                    if (!(arg instanceof MemberListCrossJoinArg) ||
088                        !((MemberListCrossJoinArg) arg).hasCalcMembers()) {
089                        arg.addConstraint(sqlQuery, baseCube);
090                    }
091                }
092            }
093    
094            /**
095             * Returns null to prevent the member/childern from being cached. There
096             * exists no valid MemberChildrenConstraint that would fetch those
097             * children that were extracted as a side effect from evaluating a non
098             * empty crossjoin
099             */
100            public MemberChildrenConstraint getMemberChildrenConstraint(
101                RolapMember parent) {
102                return null;
103            }
104    
105            /**
106             * returns a key to cache the result
107             */
108            public Object getCacheKey() {
109                List<Object> key = new ArrayList<Object>();
110                key.add(super.getCacheKey());
111                // only add args that will be retrieved through native sql;
112                // args that are sets with calculated members aren't executed
113                // natively
114                for (CrossJoinArg arg : args) {
115                    if (!(arg instanceof MemberListCrossJoinArg) ||
116                        !((MemberListCrossJoinArg) arg).hasCalcMembers()) {
117                        key.add(arg);
118                    }
119                }
120                return key;
121            }
122        }
123    
124        protected class SetEvaluator implements NativeEvaluator {
125            private final CrossJoinArg[] args;
126            private final SchemaReaderWithMemberReaderAvailable schemaReader;
127            private final TupleConstraint constraint;
128            private int maxRows = 0;
129    
130            public SetEvaluator(
131                CrossJoinArg[] args,
132                SchemaReader schemaReader,
133                TupleConstraint constraint)
134            {
135                this.args = args;
136                if (schemaReader instanceof SchemaReaderWithMemberReaderAvailable) {
137                    this.schemaReader =
138                        (SchemaReaderWithMemberReaderAvailable) schemaReader;
139                } else {
140                    this.schemaReader =
141                        new SchemaReaderWithMemberReaderCache(schemaReader);
142                }
143                this.constraint = constraint;
144            }
145    
146            public Object execute(ResultStyle desiredResultStyle) {
147                switch (desiredResultStyle) {
148                case ITERABLE:
149                    return executeList(new HighCardSqlTupleReader(constraint));
150                case MUTABLE_LIST:
151                case LIST:
152                    return executeList(new SqlTupleReader(constraint));
153                }
154                throw ResultStyleException.generate(
155                    ResultStyle.ITERABLE_MUTABLELIST_LIST,
156                    Collections.singletonList(desiredResultStyle));
157            }
158    
159            protected List executeList(final SqlTupleReader tr) {
160                tr.setMaxRows(maxRows);
161                for (CrossJoinArg arg : args) {
162                    addLevel(tr, arg);
163                }
164    
165                // lookup the result in cache; we can't return the cached
166                // result if the tuple reader contains a target with calculated
167                // members because the cached result does not include those
168                // members; so we still need to cross join the cached result
169                // with those enumerated members
170                Object key = tr.getCacheKey();
171                List<List<RolapMember>> result = cache.get(key);
172                boolean hasEnumTargets = (tr.getEnumTargetCount() > 0);
173                if (result != null && !hasEnumTargets) {
174                    if (listener != null) {
175                        TupleEvent e = new TupleEvent(this, tr);
176                        listener.foundInCache(e);
177                    }
178                    return copy(result);
179                }
180    
181                // execute sql and store the result
182                if (result == null && listener != null) {
183                    TupleEvent e = new TupleEvent(this, tr);
184                    listener.excutingSql(e);
185                }
186    
187                // if we don't have a cached result in the case where we have
188                // enumerated targets, then retrieve and cache that partial result
189                List<List<RolapMember>> partialResult = result;
190                result = null;
191                List<List<RolapMember>> newPartialResult = null;
192                if (hasEnumTargets && partialResult == null) {
193                    newPartialResult = new ArrayList<List<RolapMember>>();
194                }
195                DataSource dataSource = schemaReader.getDataSource();
196                if (args.length == 1) {
197                    result = (List) tr.readMembers(dataSource, partialResult, newPartialResult);
198                } else {
199                    result = (List) tr.readTuples(dataSource, partialResult, newPartialResult);
200                }
201    
202                if (hasEnumTargets) {
203                    if (newPartialResult != null) {
204                        cache.put(key, newPartialResult);
205                    }
206                } else {
207                    cache.put(key, result);
208                }
209                return copy(result);
210            }
211    
212            /**
213             * returns a copy of the result because its modified
214             */
215            private <T> List<T> copy(List<T> list) {
216    //            return new ArrayList<T>(list);
217                return list;
218            }
219    
220            private void addLevel(TupleReader tr, CrossJoinArg arg) {
221                RolapLevel level = arg.getLevel();
222                if (level == null) {
223                    // Level can be null if the CrossJoinArg represent
224                    // an empty set.
225                    // This is used to push down the "1 = 0" predicate
226                    // into the emerging CJ so that the entire CJ can
227                    // be natively evaluated.
228                    return;
229                }
230    
231                RolapHierarchy hierarchy = level.getHierarchy();
232                MemberReader mr = schemaReader.getMemberReader(hierarchy);
233                MemberBuilder mb = mr.getMemberBuilder();
234                Util.assertTrue(mb != null, "MemberBuilder not found");
235    
236                if (arg instanceof MemberListCrossJoinArg &&
237                    ((MemberListCrossJoinArg) arg).hasCalcMembers())
238                {
239                    // only need to keep track of the members in the case
240                    // where there are calculated members since in that case,
241                    // we produce the values by enumerating through the list
242                    // rather than generating the values through native sql
243                    tr.addLevelMembers(level, mb, arg.getMembers());
244                } else {
245                    tr.addLevelMembers(level, mb, null);
246                }
247            }
248    
249            int getMaxRows() {
250                return maxRows;
251            }
252    
253            void setMaxRows(int maxRows) {
254                this.maxRows = maxRows;
255            }
256        }
257    
258        /**
259         * "Light version" of a {@link TupleConstraint}, represents one of
260         * member.children, level.members, member.descendants, {enumeration}.
261         *
262         * @author av
263         * @since Nov 14, 2005
264         */
265        protected interface CrossJoinArg {
266            RolapLevel getLevel();
267    
268            List<RolapMember> getMembers();
269    
270            void addConstraint(SqlQuery sqlQuery, RolapCube baseCube);
271    
272            boolean isPreferInterpreter(boolean joinArg);
273        }
274    
275        /**
276         * represents one of
277         * <ul>
278         * <li>Level.Members:  member == null and level != null</li>
279         * <li>Member.Children: member != null and level = member.getLevel().getChildLevel() </li>
280         * <li>Member.Descendants: member != null and level == some level below member.getLevel()</li>
281         * </ul>
282         *
283         * @author av
284         * @since Nov 12, 2005
285         */
286        protected static class DescendantsCrossJoinArg implements CrossJoinArg {
287            RolapMember member;
288            RolapLevel level;
289    
290            public DescendantsCrossJoinArg(RolapLevel level, RolapMember member) {
291                this.level = level;
292                this.member = member;
293            }
294    
295            public RolapLevel getLevel() {
296                return level;
297            }
298    
299            public List<RolapMember> getMembers() {
300                if (member == null) {
301                    return null;
302                }
303                final List<RolapMember> list = new ArrayList<RolapMember>();
304                list.add(member);
305                return list;
306            }
307    
308            public void addConstraint(SqlQuery sqlQuery, RolapCube baseCube) {
309                if (member != null) {
310                    SqlConstraintUtils.addMemberConstraint(
311                        sqlQuery, baseCube, null, member, true);
312                }
313            }
314    
315            public boolean isPreferInterpreter(boolean joinArg) {
316                return false;
317            }
318    
319            private boolean equals(Object o1, Object o2) {
320                return o1 == null ? o2 == null : o1.equals(o2);
321            }
322    
323            public boolean equals(Object obj) {
324                if (!(obj instanceof DescendantsCrossJoinArg)) {
325                    return false;
326                }
327                DescendantsCrossJoinArg that = (DescendantsCrossJoinArg) obj;
328                if (!equals(this.level, that.level)) {
329                    return false;
330                }
331                return equals(this.member, that.member);
332            }
333    
334            public int hashCode() {
335                int c = 1;
336                if (level != null) {
337                    c = level.hashCode();
338                }
339                if (member != null) {
340                    c = 31 * c + member.hashCode();
341                }
342                return c;
343            }
344        }
345    
346        /**
347         * Represents an enumeration {member1, member2, ...}.
348         * All members must to the same level and are non-calculated.
349         *
350         * @author av
351         * @since Nov 14, 2005
352         */
353        protected static class MemberListCrossJoinArg implements CrossJoinArg {
354            private List<RolapMember> members;
355            private RolapLevel level = null;
356            private boolean restrictMemberTypes;
357            private boolean hasCalcMembers;
358            private boolean hasNonCalcMembers;
359            private boolean hasAllMember;
360    
361            private MemberListCrossJoinArg(
362                RolapLevel level, List<RolapMember> members, boolean restrictMemberTypes,
363                boolean hasCalcMembers, boolean hasNonCalcMembers, boolean hasAllMember) {
364                this.level = level;
365                this.members = members;
366                this.restrictMemberTypes = restrictMemberTypes;
367                this.hasCalcMembers = hasCalcMembers;
368                this.hasNonCalcMembers = hasNonCalcMembers;
369                this.hasAllMember = hasAllMember;
370            }
371    
372            /**
373             * Creates an instance of {@link RolapNativeSet.CrossJoinArg},
374             * or returns null if the arguments are invalid. This method also
375             * records properties of the member list such as containing
376             * calc/non calc members, and containing the All member.
377             *
378             * <p>If restrictMemberTypes is set, then the resulting argument could
379             * contain calculated members. The newly created CrossJoinArg is marked
380             * appropriately for special handling downstream.
381             *
382             * <p>If restrictMemberTypes is false, then the resulting argument
383             * contains non-calculated members of the same level (after filtering
384             * out any null members).
385             *
386             * @param evaluator the current evaluator
387             * @param args members in the list
388             * @param restrictMemberTypes whether calculated members are allowed
389             * @return MemberListCrossJoinArg if member list is well formed,
390             * NULL if not.
391             */
392            static CrossJoinArg create(
393                RolapEvaluator evaluator,
394                final List<RolapMember> args,
395                final boolean restrictMemberTypes)
396            {
397                // First check that the member list will not result in a predicate
398                // longer than the underlying DB could support.
399                if (!isArgSizeSupported(evaluator, args.size())) {
400                    return null;
401                }
402    
403                RolapLevel level = null;
404                RolapLevel nullLevel = null;
405                boolean hasCalcMembers = false;
406                boolean hasNonCalcMembers = false;
407    
408                // Crossjoin Arg is an empty member list.
409                // This is used to push down the constant "false" condition to the
410                // native evaluator.
411                if (args.size() == 0) {
412                    hasNonCalcMembers = true;
413                }
414                boolean hasAllMember = false;
415                int nNullMembers = 0;
416                try {
417                    for (RolapMember m : args) {
418                        if (m.isNull()) {
419                            // we're going to filter out null members anyway;
420                            // don't choke on the fact that their level
421                            // doesn't match that of others
422                            nullLevel = m.getLevel();
423                            ++nNullMembers;
424                            continue;
425                        }
426    
427                        // If "All" member, native evaluation is not possible
428                        // because "All" member does not have a corresponding
429                        // relational representation.
430                        //
431                        // "All" member is ignored during SQL generation.
432                        // The complete MDX query can be evaluated natively only
433                        // if there is non all member on at least one level;
434                        // otherwise the generated SQL is an empty string.
435                        // See SqlTupleReader.addLevelMemberSql()
436                        //
437                        if (m.isAll()) {
438                            hasAllMember = true;
439                        }
440    
441                        if (m.isCalculated()) {
442                            if (restrictMemberTypes) {
443                                return null;
444                            }
445                            hasCalcMembers = true;
446                        } else {
447                            hasNonCalcMembers = true;
448                        }
449                        if (level == null) {
450                            level = m.getLevel();
451                        } else if (!level.equals(m.getLevel())) {
452                            // Members should be on the same level.
453                            return null;
454                        }
455                    }
456                } catch (ClassCastException cce) {
457                    return null;
458                }
459                if (level == null) {
460                    // all members were null; use an arbitrary one of the
461                    // null levels since the SQL predicate is going to always
462                    // fail anyway
463                    level = nullLevel;
464                }
465    
466                // level will be null for an empty CJ input that is pushed down
467                // to the native evaluator.
468                // This case is not treated as a non-native input.
469                if ((level != null) && (!isSimpleLevel(level))) {
470                    return null;
471                }
472                List<RolapMember> members = new ArrayList<RolapMember>();
473    
474                for (RolapMember m : args) {
475                    if (m.isNull()) {
476                        // filter out null members
477                        continue;
478                    }
479                    members.add(m);
480                }
481    
482                return new MemberListCrossJoinArg(
483                    level, members, restrictMemberTypes,
484                    hasCalcMembers, hasNonCalcMembers, hasAllMember);
485            }
486    
487            public RolapLevel getLevel() {
488                return level;
489            }
490    
491            public List<RolapMember> getMembers() {
492                return members;
493            }
494    
495            public boolean isPreferInterpreter(boolean joinArg) {
496                if (joinArg) {
497                    // If this enumeration only contains calculated members,
498                    // prefer non-native evaluation.
499                    return hasCalcMembers && !hasNonCalcMembers;
500                } else {
501                    // For non-join usage, always prefer non-native
502                    // eval, since the members are already known.
503                    return true;
504                }
505            }
506    
507            public void addConstraint(SqlQuery sqlQuery, RolapCube baseCube) {
508                SqlConstraintUtils.addMemberConstraint(
509                    sqlQuery, baseCube, null,
510                    members, restrictMemberTypes, true);
511            }
512    
513            /**
514             * Returns whether the input CJ arg is empty.
515             *
516             * <p>This is used to selectively push down empty input arg into the
517             * native evaluator.
518             *
519             * @return whether the input CJ arg is empty
520             */
521            public boolean isEmptyCrossJoinArg() {
522                return (level == null && members.size() == 0);
523            }
524    
525            public boolean hasCalcMembers() {
526                return hasCalcMembers;
527            }
528    
529            public boolean hasAllMember() {
530                return hasAllMember;
531            }
532    
533            public int hashCode() {
534                int c = 12;
535                for (RolapMember member : members) {
536                    c = 31 * c + member.hashCode();
537                }
538                if (restrictMemberTypes) {
539                    c += 1;
540                }
541                return c;
542            }
543    
544            public boolean equals(Object obj) {
545                if (!(obj instanceof MemberListCrossJoinArg)) {
546                    return false;
547                }
548                MemberListCrossJoinArg that = (MemberListCrossJoinArg) obj;
549                if (this.restrictMemberTypes != that.restrictMemberTypes) {
550                    return false;
551                }
552                for (int i = 0; i < members.size(); i++) {
553                    if (this.members.get(i) != that.members.get(i)) {
554                        return false;
555                    }
556                }
557                return true;
558            }
559        }
560    
561        /**
562         * Checks for Descendants(&lt;member&gt;, &lt;Level&gt;)
563         *
564         * @return an {@link CrossJoinArg} instance describing the Descendants
565         *   function, or null if <code>fun</code> represents something else.
566         */
567        protected CrossJoinArg checkDescendants(
568            Role role,
569            FunDef fun,
570            Exp[] args)
571        {
572            if (!"Descendants".equalsIgnoreCase(fun.getName())) {
573                return null;
574            }
575            if (args.length != 2) {
576                return null;
577            }
578            if (!(args[0] instanceof MemberExpr)) {
579                return null;
580            }
581            RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember();
582            if (member.isCalculated()) {
583                return null;
584            }
585            if (!(args[1] instanceof LevelExpr)) {
586                return null;
587            }
588            RolapLevel level = (RolapLevel) ((LevelExpr) args[1]).getLevel();
589            if (!isSimpleLevel(level)) {
590                return null;
591            }
592            // Descendants of a member in an access-controlled hierarchy cannot be
593            // converted to SQL. (We could be smarter; we don't currently notice
594            // when the member is in a part of the hierarchy that is not
595            // access-controlled.)
596            final Access access = role.getAccess(level.getHierarchy());
597            switch (access) {
598            case ALL:
599                break;
600            default:
601                return null;
602            }
603            return new DescendantsCrossJoinArg(level, member);
604        }
605    
606        /**
607         * Checks for <code>&lt;Level&gt;.Members</code>.
608         *
609         * @return an {@link CrossJoinArg} instance describing the Level.members
610         *   function, or null if <code>fun</code> represents something else.
611         */
612        protected CrossJoinArg checkLevelMembers(
613            Role role,
614            FunDef fun,
615            Exp[] args)
616        {
617            if (!"Members".equalsIgnoreCase(fun.getName())) {
618                return null;
619            }
620            if (args.length != 1) {
621                return null;
622            }
623            if (!(args[0] instanceof LevelExpr)) {
624                return null;
625            }
626            RolapLevel level = (RolapLevel) ((LevelExpr) args[0]).getLevel();
627            if (!isSimpleLevel(level)) {
628                return null;
629            }
630            // Members of a level in an access-controlled hierarchy cannot be
631            // converted to SQL. (We could be smarter; we don't currently notice
632            // when the level is in a part of the hierarchy that is not
633            // access-controlled.)
634            final Access access = role.getAccess(level.getHierarchy());
635            switch (access) {
636            case ALL:
637                break;
638            default:
639                return null;
640            }
641            return new DescendantsCrossJoinArg(level, null);
642        }
643    
644        /**
645         * Checks for <code>&lt;Member&gt;.Children</code>.
646         *
647         * @return an {@link CrossJoinArg} instance describing the member.children
648         *   function, or null if <code>fun</code> represents something else.
649         */
650        protected CrossJoinArg checkMemberChildren(
651            Role role,
652            FunDef fun,
653            Exp[] args)
654        {
655            if (!"Children".equalsIgnoreCase(fun.getName())) {
656                return null;
657            }
658            if (args.length != 1) {
659                return null;
660            }
661    
662            // Note: <Dimension>.Children is not recognized as a native expression.
663            if (!(args[0] instanceof MemberExpr)) {
664                return null;
665            }
666            RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember();
667            if (member.isCalculated()) {
668                return null;
669            }
670            RolapLevel level = member.getLevel();
671            level = (RolapLevel) level.getChildLevel();
672            if (level == null || !isSimpleLevel(level)) {
673                // no child level
674                return null;
675            }
676            // Children of a member in an access-controlled hierarchy cannot be
677            // converted to SQL. (We could be smarter; we don't currently notice
678            // when the member is in a part of the hierarchy that is not
679            // access-controlled.)
680            final Access access = role.getAccess(level.getHierarchy());
681            switch (access) {
682            case ALL:
683                break;
684            default:
685                return null;
686            }
687            return new DescendantsCrossJoinArg(level, member);
688        }
689    
690        private static boolean isArgSizeSupported(
691            RolapEvaluator evaluator,
692            int argSize) {
693            boolean argSizeNotSupported = false;
694    
695            // Note: srg size 0 is accepted as valid CJ argument
696            // This is used to push down the "1 = 0" predicate
697            // into the emerging CJ so that the entire CJ can
698            // be natively evaluated.
699    
700            // First check that the member list will not result in a predicate
701            // longer than the underlying DB could support.
702            if (!evaluator.getDialect().supportsUnlimitedValueList() &&
703                argSize > MondrianProperties.instance().MaxConstraints.get()) {
704                argSizeNotSupported = true;
705            }
706    
707            return (!argSizeNotSupported);
708        }
709    
710        /**
711         * Checks for a set constructor, <code>{member1, member2,
712         * &#46;&#46;&#46;}</code> that does not contain calculated members.
713         *
714         * @return an {@link CrossJoinArg} instance describing the enumeration,
715         *    or null if <code>fun</code> represents something else.
716         */
717        protected CrossJoinArg checkEnumeration(RolapEvaluator evaluator,
718            FunDef fun, Exp[] args) {
719            // Return null if not the expected funciton name or input size.
720            if (!"{}".equalsIgnoreCase(fun.getName()) ||
721                !isArgSizeSupported(evaluator, args.length)) {
722                return null;
723            }
724    
725            List<RolapMember> memberList = new ArrayList<RolapMember>();
726            for (int i = 0; i < args.length; ++i) {
727                if (!(args[i] instanceof MemberExpr) ||
728                    ((MemberExpr) args[i]).getMember().isCalculated()) {
729                    // also returns null if any member is calculated
730                    return null;
731                }
732                memberList.add((RolapMember)
733                    (((MemberExpr)args[i]).getMember()));
734            }
735    
736            return MemberListCrossJoinArg.create(evaluator, memberList, restrictMemberTypes());
737        }
738    
739        /**
740         * Checks for <code>CrossJoin(&lt;set1&gt;, &lt;set2&gt)</code>, where
741         * set1 and set2 are one of
742         * <code>member.children</code>, <code>level.members</code> or
743         * <code>member.descendants</code>.
744         *
745         * @param evaluator RolapEvaluator to use if inputs are to be evaluated
746         * @param fun the CrossJoin function, either "CrossJoin" or "NonEmptyCrossJoin".
747         * @param args inputs to the CrossJoin
748         * @return array of CrossJoinArg representing the inputs.
749         */
750        protected CrossJoinArg[] checkCrossJoin(
751            RolapEvaluator evaluator,
752            FunDef fun,
753            Exp[] args) {
754            // is this "CrossJoin([A].children, [B].children)"
755            if (!"Crossjoin".equalsIgnoreCase(fun.getName()) &&
756                !"NonEmptyCrossJoin".equalsIgnoreCase(fun.getName()))
757            {
758                return null;
759            }
760            if (args.length != 2) {
761                return null;
762            }
763            ExpCompiler compiler = evaluator.getQuery().createCompiler();
764    
765            // Check if the arguments can be natively evaluated.
766            // If not, try evaluating this argument and turning the result into
767            // MemberListCrossJoinArg.
768            // If either the inputs can be natively evaluated, or the result list
769            CrossJoinArg[] arg0 = checkCrossJoinArg(evaluator, args[0]);
770            if (arg0 == null) {
771                if (MondrianProperties.instance().ExpandNonNative.get()) {
772                    ListCalc listCalc0 = compiler.compileList(args[0]);
773                    List<RolapMember> list0 = Util.cast(listCalc0.evaluateList(evaluator));
774                    // Prevent the case when the second argument size is too large
775                    if (list0 != null) {
776                        Util.checkCJResultLimit(list0.size());
777                    }
778                    CrossJoinArg arg =
779                        MemberListCrossJoinArg.create(evaluator, list0, restrictMemberTypes());
780                    if (arg != null) {
781                        arg0 = new CrossJoinArg[] {arg};
782                    } else {
783                        return null;
784                    }
785                } else {
786                    return null;
787                }
788            }
789    
790            CrossJoinArg[] arg1 = checkCrossJoinArg(evaluator, args[1]);
791            if (arg1 == null) {
792                if (MondrianProperties.instance().ExpandNonNative.get()) {
793                    ListCalc listCalc1 =
794                        (MemberListCalc) compiler.compileList(args[1]);
795                    List<RolapMember> list1 = Util.cast(listCalc1.evaluateList(evaluator));
796                    // Prevent the case when the second argument size is too large
797                    if (list1 != null) {
798                        Util.checkCJResultLimit(list1.size());
799                    }
800    
801                    CrossJoinArg arg =
802                        MemberListCrossJoinArg.create(evaluator, list1, restrictMemberTypes());
803                    if (arg != null) {
804                        arg1 = new CrossJoinArg[] {arg};
805                    } else {
806                        return null;
807                    }
808                } else {
809                    return null;
810                }
811            }
812    
813            CrossJoinArg[] ret = new CrossJoinArg[arg0.length + arg1.length];
814            System.arraycopy(arg0, 0, ret, 0, arg0.length);
815            System.arraycopy(arg1, 0, ret, arg0.length, arg1.length);
816            return ret;
817        }
818    
819        /**
820         * Scans for memberChildren, levelMembers, memberDescendants, crossJoin.
821         */
822        protected CrossJoinArg[] checkCrossJoinArg(
823            RolapEvaluator evaluator,
824            Exp exp)
825        {
826            if (exp instanceof NamedSetExpr) {
827                NamedSet namedSet = ((NamedSetExpr) exp).getNamedSet();
828                exp = namedSet.getExp();
829            }
830            if (!(exp instanceof ResolvedFunCall)) {
831                return null;
832            }
833            final ResolvedFunCall funCall = (ResolvedFunCall) exp;
834            FunDef fun = funCall.getFunDef();
835            Exp[] args = funCall.getArgs();
836    
837            final Role role = evaluator.getSchemaReader().getRole();
838            CrossJoinArg arg;
839            arg = checkMemberChildren(role, fun, args);
840            if (arg != null) {
841                return new CrossJoinArg[] {arg};
842            }
843            arg = checkLevelMembers(role, fun, args);
844            if (arg != null) {
845                return new CrossJoinArg[] {arg};
846            }
847            arg = checkDescendants(role, fun, args);
848            if (arg != null) {
849                return new CrossJoinArg[] {arg};
850            }
851            arg = checkEnumeration(evaluator, fun, args);
852            if (arg != null) {
853                return new CrossJoinArg[] {arg};
854            }
855            return checkCrossJoin(evaluator, fun, args);
856        }
857    
858        /**
859         * Ensures that level is not ragged and not a parent/child level.
860         */
861        protected static boolean isSimpleLevel(RolapLevel level) {
862            RolapHierarchy hier = level.getHierarchy();
863            // does not work with ragged hierarchies
864            if (hier.isRagged()) {
865                return false;
866            }
867            // does not work with parent/child
868            if (level.isParentChild()) {
869                return false;
870            }
871            // does not work for measures
872            if (level.isMeasure()) {
873                return false;
874            }
875            return true;
876        }
877    
878        /**
879         * Tests whether non-native evaluation is preferred for the
880         * given arguments.
881         *
882         * @param joinArg true if evaluating a cross-join; false if
883         * evaluating a single-input expression such as filter
884         *
885         * @return true if <em>all</em> args prefer the interpreter
886         */
887        protected boolean isPreferInterpreter(
888            CrossJoinArg[] args, boolean joinArg) {
889            for (CrossJoinArg arg : args) {
890                if (!arg.isPreferInterpreter(joinArg)) {
891                    return false;
892                }
893            }
894            return true;
895        }
896    
897        /** disable garbage collection for test */
898        void useHardCache(boolean hard) {
899            if (hard) {
900                cache = new HardSmartCache();
901            } else {
902                cache = new SoftSmartCache();
903            }
904        }
905    
906        /**
907         * Override current members in position by default members in
908         * hierarchies which are involved in this filter/topcount.
909         * Stores the RolapStoredMeasure into the context because that is needed to
910         * generate a cell request to constraint the sql.
911         *
912         * The current context may contain a calculated measure, this measure
913         * was translated into an sql condition (filter/topcount). The measure
914         * is not used to constrain the result but only to access the star.
915         *
916         * @see RolapAggregationManager#makeRequest(RolapEvaluator)
917         */
918        protected RolapEvaluator overrideContext(
919            RolapEvaluator evaluator,
920            CrossJoinArg[] cargs,
921            RolapStoredMeasure storedMeasure)
922        {
923            SchemaReader schemaReader = evaluator.getSchemaReader();
924            RolapEvaluator newEvaluator = (RolapEvaluator) evaluator.push();
925            for (CrossJoinArg carg : cargs) {
926                RolapLevel level = carg.getLevel();
927                if (level != null) {
928                    Hierarchy hierarchy = level.getHierarchy();
929                    Member defaultMember =
930                        schemaReader.getHierarchyDefaultMember(hierarchy);
931                    newEvaluator.setContext(defaultMember);
932                }
933            }
934            if (storedMeasure != null)
935                newEvaluator.setContext(storedMeasure);
936            return newEvaluator;
937        }
938    
939    
940        public interface SchemaReaderWithMemberReaderAvailable extends SchemaReader {
941            MemberReader getMemberReader(Hierarchy hierarchy);
942        }
943    
944        private static class SchemaReaderWithMemberReaderCache
945            extends DelegatingSchemaReader
946            implements SchemaReaderWithMemberReaderAvailable {
947            private final Map<Hierarchy,MemberReader> hierarchyReaders =
948                new HashMap<Hierarchy, MemberReader>();
949    
950            SchemaReaderWithMemberReaderCache(SchemaReader schemaReader) {
951                super(schemaReader);
952            }
953    
954            public synchronized MemberReader getMemberReader(Hierarchy hierarchy) {
955                MemberReader memberReader = hierarchyReaders.get(hierarchy);
956                if (memberReader == null) {
957                    memberReader =
958                        ((RolapHierarchy) hierarchy).createMemberReader(
959                            schemaReader.getRole());
960                    hierarchyReaders.put(hierarchy, memberReader);
961                }
962                return memberReader;
963            }
964        }
965    }
966    
967    // End RolapNativeSet.java