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