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(<member>, <Level>)
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><Level>.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><Member>.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 * ...}</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(<set1>, <set2>)</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