KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > RolapNativeSet


1 /*
2 // This software is subject to the terms of the Common Public License
3 // Agreement, available at the following URL:
4 // http://www.opensource.org/licenses/cpl.html.
5 // Copyright (C) 2004-2005 TONBELLER AG
6 // All Rights Reserved.
7 // You must accept the terms of that agreement to use this software.
8 */

9 package mondrian.rolap;
10
11 import java.util.*;
12 import java.sql.*;
13
14 import mondrian.calc.ExpCompiler.ResultStyle;
15 import mondrian.olap.*;
16 import mondrian.rolap.TupleReader.MemberBuilder;
17 import mondrian.rolap.cache.HardSmartCache;
18 import mondrian.rolap.cache.SmartCache;
19 import mondrian.rolap.cache.SoftSmartCache;
20 import mondrian.rolap.sql.MemberChildrenConstraint;
21 import mondrian.rolap.sql.SqlQuery;
22 import mondrian.rolap.sql.TupleConstraint;
23 import mondrian.mdx.*;
24
25 import org.apache.log4j.Logger;
26
27 import javax.sql.DataSource JavaDoc;
28
29 /**
30  * Analyses set expressions and executes them in SQL if possible.
31  * Supports crossjoin, member.children, level.members and member.descendants -
32  * all in non empty mode, i.e. there is a join to the fact table.<p/>
33  *
34  * TODO: the order of the result is different from the order of the
35  * enumeration. Should sort.
36  *
37  * @author av
38  * @since Nov 12, 2005
39  * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapNativeSet.java#23 $
40  */

41 public abstract class RolapNativeSet extends RolapNative {
42     protected static final Logger LOGGER = Logger.getLogger(RolapNativeSet.class);
43
44     private SmartCache<Object JavaDoc, List<List<RolapMember>>> cache =
45         new SoftSmartCache<Object JavaDoc, List<List<RolapMember>>>();
46
47     /**
48      * Returns whether calculated members should be accepted (and ignored).
49      *
50      * <p>If true, calc members will be ignored and the computation still will
51      * be done in SQL returning more members than requested.
52      * If false, expressions containing calculated members will rejected to the
53      * interpreter.
54      */

55     protected abstract boolean isStrict();
56
57     /**
58      * Constraint for non empty {crossjoin, member.children,
59      * member.descendants, level.members}
60      */

61     protected static abstract class SetConstraint extends SqlContextConstraint {
62         CrossJoinArg[] args;
63
64         SetConstraint(CrossJoinArg[] args, RolapEvaluator evaluator, boolean strict) {
65             super(evaluator, strict);
66             this.args = args;
67         }
68
69         /**
70          * if there is a crossjoin, we need to join the fact table - even if the
71          * evalutaor context is empty.
72          */

73         protected boolean isJoinRequired() {
74             return args.length > 1 || super.isJoinRequired();
75         }
76
77         public void addConstraint(
78             SqlQuery sqlQuery,
79             Map<RolapLevel, RolapStar.Column> levelToColumnMap) {
80             super.addConstraint(sqlQuery, levelToColumnMap);
81             for (CrossJoinArg arg : args) {
82                 // if the cross join argument has calculated members in its
83
// enumerated set, ignore the constraint since we won't
84
// produce that set through the native sql and instead
85
// will simply enumerate through the members in the set
86
if (!(arg instanceof MemberListCrossJoinArg) ||
87                     !((MemberListCrossJoinArg) arg).hasCalcMembers()) {
88                     arg.addConstraint(sqlQuery, levelToColumnMap);
89                 }
90             }
91         }
92
93         /**
94          * returns null to prevent the member/childern from being cached. There exists
95          * no valid MemberChildrenConstraint that would fetch those children that were
96          * extracted as a side effect from evaluating a non empty crossjoin
97          */

98         public MemberChildrenConstraint getMemberChildrenConstraint(RolapMember parent) {
99             return null;
100         }
101
102         /**
103          * returns a key to cache the result
104          */

105         public Object JavaDoc getCacheKey() {
106             List<Object JavaDoc> key = new ArrayList<Object JavaDoc>();
107             key.add(super.getCacheKey());
108             // only add args that will be retrieved through native sql;
109
// args that are sets with calculated members aren't executed
110
// natively
111
for (CrossJoinArg arg : args) {
112                 if (!(arg instanceof MemberListCrossJoinArg) ||
113                     !((MemberListCrossJoinArg) arg).hasCalcMembers()) {
114                     key.add(arg);
115                 }
116             }
117             return key;
118         }
119     }
120
121     protected class SetEvaluator implements NativeEvaluator {
122         private CrossJoinArg[] args;
123         private SchemaReader schemaReader;
124         private TupleConstraint constraint;
125         private int maxRows = 0;
126
127         public SetEvaluator(
128                 CrossJoinArg[] args,
129                 SchemaReader schemaReader,
130                 TupleConstraint constraint) {
131             this.args = args;
132             this.schemaReader = schemaReader;
133             this.constraint = constraint;
134         }
135
136         public Object JavaDoc execute(ResultStyle desiredResultStyle) {
137             switch (desiredResultStyle) {
138             case ITERABLE :
139                 return executeIterable();
140             case MUTABLE_LIST :
141             case LIST :
142                 return executeList();
143             }
144             throw ResultStyleException.generate(
145                 new ResultStyle[] {
146                     ResultStyle.ITERABLE,
147                     ResultStyle.MUTABLE_LIST,
148                     ResultStyle.LIST
149                 },
150                 new ResultStyle[] {
151                     desiredResultStyle
152                 }
153             );
154         }
155         protected Object JavaDoc executeIterable() {
156             final List list = executeList();
157             if (args.length == 1) {
158                 return new Iterable JavaDoc<Member>() {
159                     public Iterator<Member> iterator() {
160                         return new Iterator<Member>() {
161                             int index = 0;
162                             public boolean hasNext() {
163                                 return (index < list.size());
164                             }
165                             public Member next() {
166                                 return (Member) list.get(index++);
167                             }
168                             public void remove() {
169                                 throw new UnsupportedOperationException JavaDoc("remove");
170                             }
171                         };
172                     }
173                 };
174             } else {
175                 return new Iterable JavaDoc<Member[]>() {
176                     public Iterator<Member[]> iterator() {
177                         return new Iterator<Member[]>() {
178                             int index = 0;
179                             public boolean hasNext() {
180                                 return (index < list.size());
181                             }
182                             public Member[] next() {
183                                 return (Member[]) list.get(index++);
184                             }
185                             public void remove() {
186                                 throw new UnsupportedOperationException JavaDoc("remove");
187                             }
188                         };
189                     }
190                 };
191             }
192         }
193         protected List executeList() {
194             SqlTupleReader tr = new SqlTupleReader(constraint);
195             tr.setMaxRows(maxRows);
196             for (CrossJoinArg arg : args) {
197                 addLevel(tr, arg);
198             }
199
200             // lookup the result in cache; we can't return the cached
201
// result if the tuple reader contains a target with calculated
202
// members because the cached result does not include those
203
// members; so we still need to cross join the cached result
204
// with those enumerated members
205
Object JavaDoc key = tr.getCacheKey();
206             List<List<RolapMember>> result = cache.get(key);
207             boolean hasEnumTargets = (tr.getEnumTargetCount() > 0);
208             if (result != null && !hasEnumTargets) {
209                 if (listener != null) {
210                     TupleEvent e = new TupleEvent(this, tr);
211                     listener.foundInCache(e);
212                 }
213                 return copy(result);
214             }
215
216             // execute sql and store the result
217
if (result == null && listener != null) {
218                 TupleEvent e = new TupleEvent(this, tr);
219                 listener.excutingSql(e);
220             }
221
222             // if we don't have a cached result in the case where we have
223
// enumerated targets, then retrieve and cache that partial result
224
List<List<RolapMember>> partialResult = result;
225             result = null;
226             List<List<RolapMember>> newPartialResult = null;
227             if (hasEnumTargets && partialResult == null) {
228                 newPartialResult = new ArrayList<List<RolapMember>>();
229             }
230             DataSource JavaDoc dataSource = schemaReader.getDataSource();
231             if (args.length == 1) {
232                 result = (List) tr.readMembers(dataSource, partialResult, newPartialResult);
233             } else {
234                 result = (List) tr.readTuples(dataSource, partialResult, newPartialResult);
235             }
236
237             if (hasEnumTargets) {
238                 if (newPartialResult != null) {
239                     cache.put(key, newPartialResult);
240                 }
241             } else {
242                 cache.put(key, result);
243             }
244             return copy(result);
245         }
246
247         /**
248          * returns a copy of the result because its modified
249          */

250         private <T> List<T> copy(List<T> list) {
251             return new ArrayList<T>(list);
252         }
253
254         private void addLevel(TupleReader tr, CrossJoinArg arg) {
255             RolapLevel level = arg.getLevel();
256             RolapHierarchy hierarchy = level.getHierarchy();
257             MemberReader mr = hierarchy.getMemberReader(schemaReader.getRole());
258             MemberBuilder mb = mr.getMemberBuilder();
259             Util.assertTrue(mb != null, "MemberBuilder not found");
260
261             if (arg instanceof MemberListCrossJoinArg &&
262                 ((MemberListCrossJoinArg) arg).hasCalcMembers())
263             {
264                 // only need to keep track of the members in the case
265
// where there are calculated members since in that case,
266
// we produce the values by enumerating through the list
267
// rather than generating the values through native sql
268
tr.addLevelMembers(level, mb, arg.getMembers());
269             } else {
270                 tr.addLevelMembers(level, mb, null);
271             }
272         }
273
274         int getMaxRows() {
275             return maxRows;
276         }
277
278         void setMaxRows(int maxRows) {
279             this.maxRows = maxRows;
280         }
281     }
282
283     /**
284      * "Light version" of a {@link TupleConstraint}, represents one of
285      * member.children, level.members, member.descendants, {enumeration}.
286      *
287      * @author av
288      * @since Nov 14, 2005
289      */

290     protected interface CrossJoinArg {
291         RolapLevel getLevel();
292
293         RolapMember[] getMembers();
294
295         void addConstraint(
296             SqlQuery sqlQuery,
297             Map<RolapLevel, RolapStar.Column> levelToColumnMap);
298
299         boolean isPreferInterpreter();
300     }
301
302     /**
303      * represents one of
304      * <ul>
305      * <li>Level.Members: member == null and level != null</li>
306      * <li>Member.Children: member != null and level = member.getLevel().getChildLevel() </li>
307      * <li>Member.Descendants: member != null and level == some level below member.getLevel()</li>
308      * </ul>
309      *
310      * @author av
311      * @since Nov 12, 2005
312      */

313     protected static class DescendantsCrossJoinArg implements CrossJoinArg {
314         RolapMember member;
315         RolapLevel level;
316
317         public DescendantsCrossJoinArg(RolapLevel level, RolapMember member) {
318             this.level = level;
319             this.member = member;
320         }
321
322         public RolapLevel getLevel() {
323             return level;
324         }
325
326         public RolapMember[] getMembers() {
327             if (member == null) {
328                 return null;
329             }
330             return new RolapMember[] { member };
331         }
332
333         public boolean isPreferInterpreter() {
334             return false;
335         }
336
337         private boolean equals(Object JavaDoc o1, Object JavaDoc o2) {
338             return o1 == null ? o2 == null : o1.equals(o2);
339         }
340
341         public boolean equals(Object JavaDoc obj) {
342             if (!(obj instanceof DescendantsCrossJoinArg)) {
343                 return false;
344             }
345             DescendantsCrossJoinArg that = (DescendantsCrossJoinArg) obj;
346             if (!equals(this.level, that.level)) {
347                 return false;
348             }
349             return equals(this.member, that.member);
350         }
351
352         public int hashCode() {
353             int c = 1;
354             if (level != null) {
355                 c = level.hashCode();
356             }
357             if (member != null) {
358                 c = 31 * c + member.hashCode();
359             }
360             return c;
361         }
362
363         public void addConstraint(
364             SqlQuery sqlQuery,
365             Map<RolapLevel, RolapStar.Column> levelToColumnMap) {
366             if (member != null) {
367                 SqlConstraintUtils.addMemberConstraint(
368                     sqlQuery, levelToColumnMap, 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 strict;
384         private boolean hasCalcMembers;
385
386         private MemberListCrossJoinArg(
387             RolapLevel level, RolapMember[] members, boolean strict,
388             boolean hasCalcMembers) {
389             this.level = level;
390             this.members = members;
391             this.strict = strict;
392             this.hasCalcMembers = hasCalcMembers;
393         }
394
395         /**
396          * Creates an instance of {@link RolapNativeSet.CrossJoinArg},
397          * or returns null if the arguments are invalid.
398          *
399          * <p>To be valid, the arguments must be non-calculated members of the
400          * same level.
401          */

402         static CrossJoinArg create(Exp[] args, boolean strict) {
403             if (args.length == 0) {
404                 return null;
405             }
406             RolapLevel level = null;
407             boolean hasCalcMembers = false;
408             for (int i = 0; i < args.length; i++) {
409                 if (!(args[i] instanceof MemberExpr)) {
410                     return null;
411                 }
412                 RolapMember m = (RolapMember) ((MemberExpr) args[i]).getMember();
413                 if (m.isCalculated()) {
414                     if (strict) {
415                         return null;
416                     }
417                     hasCalcMembers = true;
418                 }
419                 if (i == 0) {
420                     level = m.getLevel();
421                 } else if (!level.equals(m.getLevel())) {
422                     return null;
423                 }
424             }
425             if (!isSimpleLevel(level)) {
426                 return null;
427             }
428             RolapMember[] members = new RolapMember[args.length];
429             for (int i = 0; i < members.length; i++) {
430                 members[i] = (RolapMember) ((MemberExpr) args[i]).getMember();
431             }
432             return new MemberListCrossJoinArg(
433                 level, members, strict, hasCalcMembers);
434         }
435
436         public RolapLevel getLevel() {
437             return level;
438         }
439
440         public RolapMember[] getMembers() {
441             return members;
442         }
443
444         public boolean isPreferInterpreter() {
445             return true;
446         }
447
448         public boolean hasCalcMembers() {
449             return hasCalcMembers;
450         }
451
452         public int hashCode() {
453             int c = 12;
454             for (RolapMember member : members) {
455                 c = 31 * c + member.hashCode();
456             }
457             if (strict) {
458                 c += 1;
459             }
460             return c;
461         }
462
463         public boolean equals(Object JavaDoc obj) {
464             if (!(obj instanceof MemberListCrossJoinArg)) {
465                 return false;
466             }
467             MemberListCrossJoinArg that = (MemberListCrossJoinArg) obj;
468             if (this.strict != that.strict) {
469                 return false;
470             }
471             for (int i = 0; i < members.length; i++) {
472                 if (this.members[i] != that.members[i]) {
473                     return false;
474                 }
475             }
476             return true;
477         }
478
479         public void addConstraint(
480             SqlQuery sqlQuery,
481             Map<RolapLevel, RolapStar.Column> levelToColumnMap) {
482             SqlConstraintUtils.addMemberConstraint(
483                 sqlQuery, levelToColumnMap, null, Arrays.asList(members),
484                 strict, true);
485         }
486     }
487
488     /**
489      * Checks for Descendants(&lt;member&gt;, &lt;Level&gt;)
490      *
491      * @return an {@link CrossJoinArg} instance describing the Descendants
492      * function, or null if <code>fun</code> represents something else.
493      */

494     protected CrossJoinArg checkDescendants(FunDef fun, Exp[] args) {
495         if (!"Descendants".equalsIgnoreCase(fun.getName())) {
496             return null;
497         }
498         if (args.length != 2) {
499             return null;
500         }
501         if (!(args[0] instanceof MemberExpr)) {
502             return null;
503         }
504         RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember();
505         if (member.isCalculated()) {
506             return null;
507         }
508         if (!(args[1] instanceof LevelExpr)) {
509             return null;
510         }
511         RolapLevel level = (RolapLevel) ((LevelExpr) args[1]).getLevel();
512         if (!isSimpleLevel(level)) {
513             return null;
514         }
515         return new DescendantsCrossJoinArg(level, member);
516     }
517
518     /**
519      * Checks for <code>&lt;Level&gt;.Members</code>.
520      *
521      * @return an {@link CrossJoinArg} instance describing the Level.members
522      * function, or null if <code>fun</code> represents something else.
523      */

524     protected CrossJoinArg checkLevelMembers(FunDef fun, Exp[] args) {
525         if (!"Members".equalsIgnoreCase(fun.getName())) {
526             return null;
527         }
528         if (args.length != 1) {
529             return null;
530         }
531         if (!(args[0] instanceof LevelExpr)) {
532             return null;
533         }
534         RolapLevel level = (RolapLevel) ((LevelExpr) args[0]).getLevel();
535         if (!isSimpleLevel(level)) {
536             return null;
537         }
538         return new DescendantsCrossJoinArg(level, null);
539     }
540
541     /**
542      * Checks for <code>&lt;Member&gt;.Children</code>.
543      *
544      * @return an {@link CrossJoinArg} instance describing the member.children
545      * function, or null if <code>fun</code> represents something else.
546      */

547     protected CrossJoinArg checkMemberChildren(FunDef fun, Exp[] args) {
548         if (!"Children".equalsIgnoreCase(fun.getName())) {
549             return null;
550         }
551         if (args.length != 1) {
552             return null;
553         }
554         if (!(args[0] instanceof MemberExpr)) {
555             return null;
556         }
557         RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember();
558         if (member.isCalculated()) {
559             return null;
560         }
561         RolapLevel level = member.getLevel();
562         level = (RolapLevel) level.getChildLevel();
563         if (level == null || !isSimpleLevel(level)) {
564             // no child level
565
return null;
566         }
567         return new DescendantsCrossJoinArg(level, member);
568     }
569
570     /**
571      * Checks for a set constructor, <code>{member1, member2,
572      * &#46;&#46;&#46;}</code>.
573      *
574      * @return an {@link CrossJoinArg} instance describing the enumeration,
575      * or null if <code>fun</code> represents something else.
576      */

577     protected CrossJoinArg checkEnumeration(FunDef fun, Exp[] args) {
578         if (!"{}".equalsIgnoreCase(fun.getName())) {
579             return null;
580         }
581         return MemberListCrossJoinArg.create(args, isStrict());
582     }
583
584     /**
585      * Checks for <code>CrossJoin(&lt;set1&gt;, &lt;set2&gt)</code>, where
586      * set1 and set2 are one of
587      * <code>member.children</code>, <code>level.members</code> or
588      * <code>member.descendants</code>.
589      */

590     protected CrossJoinArg[] checkCrossJoin(FunDef fun, Exp[] args) {
591         // is this "CrossJoin([A].children, [B].children)"
592
if (!"Crossjoin".equalsIgnoreCase(fun.getName()) &&
593             !"NonEmptyCrossJoin".equalsIgnoreCase(fun.getName()))
594         {
595             return null;
596         }
597         if (args.length != 2) {
598             return null;
599         }
600         CrossJoinArg[] arg0 = checkCrossJoinArg(args[0]);
601         if (arg0 == null) {
602             return null;
603         }
604         CrossJoinArg[] arg1 = checkCrossJoinArg(args[1]);
605         if (arg1 == null) {
606             return null;
607         }
608         CrossJoinArg[] ret = new CrossJoinArg[arg0.length + arg1.length];
609         System.arraycopy(arg0, 0, ret, 0, arg0.length);
610         System.arraycopy(arg1, 0, ret, arg0.length, arg1.length);
611         return ret;
612     }
613
614     /**
615      * Scans for memberChildren, levelMembers, memberDescendants, crossJoin.
616      */

617     protected CrossJoinArg[] checkCrossJoinArg(Exp exp) {
618         if (exp instanceof NamedSetExpr) {
619             NamedSet namedSet = ((NamedSetExpr) exp).getNamedSet();
620             exp = namedSet.getExp();
621         }
622         if (!(exp instanceof ResolvedFunCall)) {
623             return null;
624         }
625         final ResolvedFunCall funCall = (ResolvedFunCall) exp;
626         FunDef fun = funCall.getFunDef();
627         Exp[] args = funCall.getArgs();
628
629         CrossJoinArg arg;
630         arg = checkMemberChildren(fun, args);
631         if (arg != null) {
632             return new CrossJoinArg[] {arg};
633         }
634         arg = checkLevelMembers(fun, args);
635         if (arg != null) {
636             return new CrossJoinArg[] {arg};
637         }
638         arg = checkDescendants(fun, args);
639         if (arg != null) {
640             return new CrossJoinArg[] {arg};
641         }
642         arg = checkEnumeration(fun, args);
643         if (arg != null) {
644             return new CrossJoinArg[] {arg};
645         }
646         return checkCrossJoin(fun, args);
647     }
648
649     /**
650      * Ensures that level is not ragged and not a parent/child level.
651      */

652     protected static boolean isSimpleLevel(RolapLevel level) {
653         RolapHierarchy hier = level.getHierarchy();
654         // does not work with ragged hierarchies
655
if (hier.isRagged()) {
656             return false;
657         }
658         // does not work with parent/child
659
if (level.isParentChild()) {
660             return false;
661         }
662         // does not work for measures
663
if (level.isMeasure()) {
664             return false;
665         }
666         return true;
667     }
668
669     /**
670      * If all involved sets are already known, like in crossjoin({a,b}, {c,d}),
671      * then use the interpreter.
672      *
673      * @return true if <em>all</em> args are prefer the interpreter
674      */

675     protected boolean isPreferInterpreter(CrossJoinArg[] args) {
676         for (CrossJoinArg arg : args) {
677             if (!arg.isPreferInterpreter()) {
678                 return false;
679             }
680         }
681         return true;
682     }
683
684     /** disable garbage collection for test */
685     void useHardCache(boolean hard) {
686         if (hard) {
687             cache = new HardSmartCache();
688         } else {
689             cache = new SoftSmartCache();
690         }
691     }
692
693     /**
694      * Override current members in position by default members in
695      * hierarchies which are involved in this filter/topcount.
696      * Stores the RolapStoredMeasure into the context because that is needed to
697      * generate a cell request to constraint the sql.
698      *
699      * The current context may contain a calculated measure, this measure
700      * was translated into an sql condition (filter/topcount). The measure
701      * is not used to constrain the result but only to access the star.
702      *
703      * @see RolapAggregationManager#makeRequest(Member[], boolean, boolean)
704      */

705     protected RolapEvaluator overrideContext(
706         RolapEvaluator evaluator,
707         CrossJoinArg[] cargs,
708         RolapStoredMeasure storedMeasure)
709     {
710         SchemaReader schemaReader = evaluator.getSchemaReader();
711         RolapEvaluator newEvaluator = (RolapEvaluator) evaluator.push();
712         for (CrossJoinArg carg : cargs) {
713             Hierarchy hierarchy = carg.getLevel().getHierarchy();
714             Member defaultMember =
715                 schemaReader.getHierarchyDefaultMember(hierarchy);
716             newEvaluator.setContext(defaultMember);
717         }
718         if (storedMeasure != null)
719             newEvaluator.setContext(storedMeasure);
720         return newEvaluator;
721     }
722 }
723
724 // End RolapNativeSet.java
725
Popular Tags