KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > SqlTupleReader


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 // Copyright (C) 2005-2007 Julian Hyde and others
7 // All Rights Reserved.
8 // You must accept the terms of that agreement to use this software.
9 */

10 package mondrian.rolap;
11
12 import mondrian.olap.*;
13 import mondrian.olap.fun.FunUtil;
14 import mondrian.resource.MondrianResource;
15 import mondrian.rolap.sql.MemberChildrenConstraint;
16 import mondrian.rolap.sql.SqlQuery;
17 import mondrian.rolap.sql.TupleConstraint;
18
19 import javax.sql.DataSource JavaDoc;
20 import java.sql.ResultSet JavaDoc;
21 import java.sql.SQLException JavaDoc;
22 import java.util.*;
23
24 /**
25  * Reads the members of a single level (level.members) or of multiple levels
26  * (crossjoin).
27  *
28  * <p>Allows the result to be restricted by a {@link TupleConstraint}. So
29  * the SqlTupleReader can also read Member.Descendants (which is level.members
30  * restricted to a common parent) and member.children (which is a special case
31  * of member.descendants). Other constraints, especially for the current slicer
32  * or evaluation context, are possible.
33  *
34  * <h3>Caching</h3>
35  *
36  * <p>When a SqlTupleReader reads level.members, it groups the result into
37  * parent/children pairs and puts them into the cache. In order that these can
38  * be found later when the children of a parent are requested, a matching
39  * constraint must be provided for every parent.
40  *
41  * <ul>
42  *
43  * <li>When reading members from a single level, then the constraint is not
44  * required to join the fact table in
45  * {@link TupleConstraint#addLevelConstraint} although it may do so to restrict
46  * the result. Also it is permitted to cache the parent/children from all
47  * members in MemberCache, so
48  * {@link TupleConstraint#getMemberChildrenConstraint(RolapMember)}
49  * should not return null.</li>
50  *
51  * <li>When reading multiple levels (i.e. we are performing a crossjoin),
52  * then we can not store the parent/child pairs in the MemberCache and
53  * {@link TupleConstraint#getMemberChildrenConstraint(RolapMember)}
54  * must return null. Also
55  * {@link TupleConstraint#addConstraint(mondrian.rolap.sql.SqlQuery, java.util.Map)}
56  * is required to join the fact table for the levels table.</li>
57  * </ul>
58  *
59  * @author av
60  * @since Nov 11, 2005
61  * @version $Id: //open/mondrian/src/main/mondrian/rolap/SqlTupleReader.java#28 $
62  */

63 public class SqlTupleReader implements TupleReader {
64     TupleConstraint constraint;
65     List<Target> targets = new ArrayList<Target>();
66     int maxRows = 0;
67
68     /**
69      * TODO: Document this class.
70      */

71     private class Target {
72         final RolapLevel level;
73         final MemberCache cache;
74
75         RolapLevel[] levels;
76         List<RolapMember> list;
77         int levelDepth;
78         boolean parentChild;
79         RolapMember[] members;
80         List<RolapMember>[] siblings;
81         final MemberBuilder memberBuilder;
82         // if set, the rows for this target come from the array rather
83
// than native sql
84
private final RolapMember[] srcMembers;
85         // current member within the current result set row
86
// for this target
87
private RolapMember currMember;
88
89         public Target(
90             RolapLevel level, MemberBuilder memberBuilder,
91             RolapMember[] srcMembers) {
92             this.level = level;
93             this.cache = memberBuilder.getMemberCache();
94             this.memberBuilder = memberBuilder;
95             this.srcMembers = srcMembers;
96         }
97
98         public void open() {
99             levels = (RolapLevel[]) level.getHierarchy().getLevels();
100             list = new ArrayList<RolapMember>();
101             levelDepth = level.getDepth();
102             parentChild = level.isParentChild();
103             // members[i] is the current member of level#i, and siblings[i]
104
// is the current member of level#i plus its siblings
105
members = new RolapMember[levels.length];
106             siblings = new List[levels.length + 1];
107         }
108
109         /**
110          * Scans a row of the resultset and creates a member
111          * for the result.
112          *
113          * @param resultSet result set to retrieve rows from
114          * @param column the column index to start with
115          *
116          * @return index of the last column read + 1
117          * @throws SQLException
118          */

119         public int addRow(ResultSet JavaDoc resultSet, int column) throws SQLException JavaDoc {
120             synchronized (cache) {
121                 return internalAddRow(resultSet, column);
122             }
123         }
124
125         private int internalAddRow(ResultSet JavaDoc resultSet, int column) throws SQLException JavaDoc {
126             RolapMember member = null;
127             if (currMember != null) {
128                 member = currMember;
129             } else {
130                 boolean checkCacheStatus=true;
131                 for (int i = 0; i <= levelDepth; i++) {
132                     RolapLevel childLevel = levels[i];
133                     if (childLevel.isAll()) {
134                         member = level.getHierarchy().getAllMember();
135                         continue;
136                     }
137                     Object JavaDoc value = resultSet.getObject(++column);
138                     if (value == null) {
139                         value = RolapUtil.sqlNullValue;
140                     }
141                     Object JavaDoc captionValue;
142                     if (childLevel.hasCaptionColumn()) {
143                         captionValue = resultSet.getObject(++column);
144                     } else {
145                         captionValue = null;
146                     }
147                     RolapMember parentMember = member;
148                     Object JavaDoc key = cache.makeKey(parentMember, value);
149                     member = cache.getMember(key, checkCacheStatus);
150                     checkCacheStatus = false; /* Only check the first time */
151                     if (member == null) {
152                         member = memberBuilder.makeMember(
153                             parentMember, childLevel, value, captionValue,
154                             parentChild, resultSet, key, column);
155                     }
156
157                     // Skip over the columns consumed by makeMember
158
if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) {
159                         ++column;
160                     }
161                     column += childLevel.getProperties().length;
162
163                     if (member != members[i]) {
164                         // Flush list we've been building.
165
List<RolapMember> children = siblings[i + 1];
166                         if (children != null) {
167                             MemberChildrenConstraint mcc = constraint
168                                 .getMemberChildrenConstraint(members[i]);
169                             if (mcc != null)
170                                 cache.putChildren(members[i], mcc, children);
171                         }
172                         // Start a new list, if the cache needs one. (We don't
173
// synchronize, so it's possible that the cache will
174
// have one by the time we complete it.)
175
MemberChildrenConstraint mcc = constraint
176                             .getMemberChildrenConstraint(member);
177                         // we keep a reference to cachedChildren so they don't get garbage collected
178
List cachedChildren = cache.getChildrenFromCache(member, mcc);
179                         if (i < levelDepth && cachedChildren == null) {
180                             siblings[i + 1] = new ArrayList<RolapMember>();
181                         } else {
182                             siblings[i + 1] = null; // don't bother building up a list
183
}
184                         // Record new current member of this level.
185
members[i] = member;
186                         // If we're building a list of siblings at this level,
187
// we haven't seen this one before, so add it.
188
if (siblings[i] != null) {
189                             if (value == RolapUtil.sqlNullValue) {
190                                 addAsOldestSibling(siblings[i], member);
191                             } else {
192                                 siblings[i].add(member);
193                             }
194                         }
195                     }
196                 }
197                 currMember = member;
198             }
199             list.add(member);
200             return column;
201         }
202
203         public List<RolapMember> close() {
204             synchronized (cache) {
205                 return internalClose();
206             }
207         }
208
209         /**
210          * Cleans up after all rows have been processed, and returns the list of
211          * members.
212          *
213          * @return list of members
214          */

215         public List<RolapMember> internalClose() {
216             for (int i = 0; i < members.length; i++) {
217                 RolapMember member = members[i];
218                 final List<RolapMember> children = siblings[i + 1];
219                 if (member != null && children != null) {
220                     MemberChildrenConstraint mcc =
221                         constraint.getMemberChildrenConstraint(member);
222                     if (mcc != null) {
223                         cache.putChildren(member, mcc, children);
224                     }
225                 }
226             }
227             return list;
228         }
229
230         /**
231          * Adds <code>member</code> just before the first element in
232          * <code>list</code> which has the same parent.
233          */

234         private void addAsOldestSibling(List<RolapMember> list, RolapMember member) {
235             int i = list.size();
236             while (--i >= 0) {
237                 RolapMember sibling = list.get(i);
238                 if (sibling.getParentMember() != member.getParentMember()) {
239                     break;
240                 }
241             }
242             list.add(i + 1, member);
243         }
244
245         public RolapLevel getLevel() {
246             return level;
247         }
248
249         public String JavaDoc toString() {
250             return level.getUniqueName();
251         }
252
253     }
254
255     public SqlTupleReader(TupleConstraint constraint) {
256         this.constraint = constraint;
257     }
258
259     public void addLevelMembers(
260         RolapLevel level,
261         MemberBuilder memberBuilder,
262         RolapMember[] srcMembers)
263     {
264         targets.add(new Target(level, memberBuilder, srcMembers));
265     }
266
267     public Object JavaDoc getCacheKey() {
268         List<Object JavaDoc> key = new ArrayList<Object JavaDoc>();
269         key.add(constraint.getCacheKey());
270         key.add(SqlTupleReader.class);
271         for (Target target : targets) {
272             // don't include the level in the key if the target isn't
273
// processed through native sql
274
if (target.srcMembers != null) {
275                 key.add(target.getLevel());
276             }
277         }
278         return key;
279     }
280
281     /**
282      * @return number of targets that contain enumerated sets with calculated
283      * members
284      */

285     public int getEnumTargetCount()
286     {
287         int enumTargetCount = 0;
288         for (Target target : targets) {
289             if (target.srcMembers != null) {
290                 enumTargetCount++;
291             }
292         }
293         return enumTargetCount;
294     }
295
296     private void prepareTuples(
297         DataSource JavaDoc dataSource,
298         List<List<RolapMember>> partialResult,
299         List<List<RolapMember>> newPartialResult)
300     {
301         String JavaDoc message = "Populating member cache with members for " + targets;
302         SqlStatement stmt = null;
303         final ResultSet JavaDoc resultSet;
304         boolean execQuery = (partialResult == null);
305         try {
306             if (execQuery) {
307                 // we're only reading tuples from the targets that are
308
// non-enum targets
309
List<Target> partialTargets = new ArrayList<Target>();
310                 for (Target target : targets) {
311                     if (target.srcMembers == null) {
312                         partialTargets.add(target);
313                     }
314                 }
315                 String JavaDoc sql = makeLevelMembersSql(dataSource);
316                 stmt = RolapUtil.executeQuery(
317                     dataSource, sql, maxRows,
318                     "SqlTupleReader.readTuples " + partialTargets,
319                     message,
320                     -1, -1);
321                 resultSet = stmt.getResultSet();
322             } else {
323                 resultSet = null;
324             }
325
326             for (Target target : targets) {
327                 target.open();
328             }
329
330             int limit = MondrianProperties.instance().ResultLimit.get();
331             int fetchCount = 0;
332
333             // determine how many enum targets we have
334
int enumTargetCount = getEnumTargetCount();
335             int[] srcMemberIdxes = null;
336             if (enumTargetCount > 0) {
337                 srcMemberIdxes = new int[enumTargetCount];
338             }
339
340             boolean moreRows;
341             int currPartialResultIdx = 0;
342             if (execQuery) {
343                 moreRows = resultSet.next();
344                 ++stmt.rowCount;
345             } else {
346                 moreRows = currPartialResultIdx < partialResult.size();
347             }
348             while (moreRows) {
349
350                 if (limit > 0 && limit < ++fetchCount) {
351                     // result limit exceeded, throw an exception
352
throw MondrianResource.instance().MemberFetchLimitExceeded
353                             .ex((long) limit);
354                 }
355
356                 if (enumTargetCount == 0) {
357                     int column = 0;
358                     for (Target target : targets) {
359                         target.currMember = null;
360                         column =
361                             target.addRow(resultSet, column);
362                     }
363                 } else {
364                     // find the first enum target, then call addTargets()
365
// to form the cross product of the row from resultSet
366
// with each of the list of members corresponding to
367
// the enumerated targets
368
int firstEnumTarget = 0;
369                     for ( ; firstEnumTarget < targets.size();
370                         firstEnumTarget++)
371                     {
372                         if (targets.get(firstEnumTarget).
373                             srcMembers != null) {
374                             break;
375                         }
376                     }
377                     List<RolapMember> partialRow;
378                     if (execQuery) {
379                         partialRow = null;
380                     } else {
381                         partialRow = partialResult.get(currPartialResultIdx);
382                     }
383                     resetCurrMembers(partialRow);
384                     addTargets(
385                         0, firstEnumTarget, enumTargetCount, srcMemberIdxes,
386                         resultSet, message);
387                     if (newPartialResult != null) {
388                         savePartialResult(newPartialResult);
389                     }
390                 }
391
392                 if (execQuery) {
393                     moreRows = resultSet.next();
394                 } else {
395                     currPartialResultIdx++;
396                     moreRows = currPartialResultIdx < partialResult.size();
397                 }
398             }
399         } catch (Exception JavaDoc e) {
400             if (stmt == null) {
401                 throw Util.newError(e, message);
402             } else {
403                 stmt.handle(e);
404             }
405         } finally {
406             if (stmt != null) {
407                 stmt.close();
408             }
409         }
410     }
411
412     public List<RolapMember> readMembers(
413         DataSource JavaDoc dataSource,
414         List<List<RolapMember>> partialResult,
415         List<List<RolapMember>> newPartialResult)
416     {
417         prepareTuples(dataSource, partialResult, newPartialResult);
418         assert targets.size() == 1;
419         return targets.get(0).close();
420     }
421
422     public List<RolapMember[]> readTuples(
423         DataSource JavaDoc jdbcConnection,
424         List<List<RolapMember>> partialResult,
425         List<List<RolapMember>> newPartialResult)
426     {
427         prepareTuples(jdbcConnection, partialResult, newPartialResult);
428
429         // List of tuples
430
int n = targets.size();
431         List<RolapMember[]> tupleList = new ArrayList<RolapMember[]>();
432         Iterator<RolapMember>[] iter = new Iterator[n];
433         for (int i = 0; i < n; i++) {
434             Target t = targets.get(i);
435             iter[i] = t.close().iterator();
436         }
437         while (iter[0].hasNext()) {
438             RolapMember[] tuples = new RolapMember[n];
439             for (int i = 0; i < n; i++) {
440                 tuples[i] = iter[i].next();
441             }
442             tupleList.add(tuples);
443         }
444
445         // need to hierarchize the columns from the enumerated targets
446
// since we didn't necessarily add them in the order in which
447
// they originally appeared in the cross product
448
int enumTargetCount = getEnumTargetCount();
449         if (enumTargetCount > 0) {
450             FunUtil.hierarchize(tupleList, false);
451         }
452         return tupleList;
453     }
454
455     /**
456      * Sets the current member for those targets that retrieve their column
457      * values from native sql
458      *
459      * @param partialRow if set, previously cached result set
460      */

461     private void resetCurrMembers(List<RolapMember> partialRow) {
462         int nativeTarget = 0;
463         for (Target target : targets) {
464             if (target.srcMembers == null) {
465                 // if we have a previously cached row, use that by picking
466
// out the column corresponding to this target; otherwise,
467
// we need to retrieve a new column value from the current
468
// result set
469
if (partialRow != null) {
470                     target.currMember = partialRow.get(nativeTarget++);
471                 } else {
472                     target.currMember = null;
473                 }
474             }
475         }
476     }
477
478     /**
479      * Recursively forms the cross product of a row retrieved through sql
480      * with each of the targets that contains an enumerated set of members.
481      *
482      * @param currEnumTargetIdx current enum target that recursion
483      * is being applied on
484      * @param currTargetIdx index within the list of a targets that
485      * currEnumTargetIdx corresponds to
486      * @param nEnumTargets number of targets that have enumerated members
487      * @param srcMemberIdxes for each enumerated target, the current member
488      * to be retrieved to form the current cross product row
489      * @param resultSet result set corresponding to rows retrieved through
490      * native sql
491      * @param message Message to issue on failure
492      */

493     private void addTargets(
494         int currEnumTargetIdx, int currTargetIdx, int nEnumTargets,
495         int[] srcMemberIdxes, ResultSet JavaDoc resultSet, String JavaDoc message) {
496
497         // loop through the list of members for the current enum target
498
Target currTarget = targets.get(currTargetIdx);
499         for (int i = 0; i < currTarget.srcMembers.length; i++) {
500             srcMemberIdxes[currEnumTargetIdx] = i;
501             // if we're not on the last enum target, recursively move
502
// to the next one
503
if (currEnumTargetIdx < nEnumTargets - 1) {
504                 int nextTargetIdx = currTargetIdx + 1;
505                 for (; nextTargetIdx < targets.size(); nextTargetIdx++) {
506                     if (targets.get(nextTargetIdx).srcMembers != null) {
507                         break;
508                     }
509                 }
510                 addTargets(
511                     currEnumTargetIdx + 1, nextTargetIdx, nEnumTargets,
512                     srcMemberIdxes, resultSet, message);
513             } else {
514                 // form a cross product using the columns from the current
515
// result set row and the current members that recursion
516
// has reached for the enum targets
517
int column = 0;
518                 int enumTargetIdx = 0;
519                 for (Target target : targets) {
520                     if (target.srcMembers == null) {
521                         try {
522                             column = target.addRow(resultSet, column);
523                         } catch (Throwable JavaDoc e) {
524                             throw Util.newError(e, message);
525                         }
526                     } else {
527                         RolapMember member =
528                             target.srcMembers[srcMemberIdxes[enumTargetIdx++]];
529                         target.list.add(member);
530                     }
531                 }
532             }
533         }
534     }
535
536     /**
537      * Retrieves the current members fetched from the targets executed
538      * through sql and form tuples, adding them to partialResult
539      *
540      * @param partialResult list containing the columns and rows corresponding
541      * to data fetched through sql
542      */

543     private void savePartialResult(List<List<RolapMember>> partialResult) {
544         List<RolapMember> row = new ArrayList<RolapMember>();
545         for (Target target : targets) {
546             if (target.srcMembers == null) {
547                 row.add(target.currMember);
548             }
549         }
550         partialResult.add(row);
551     }
552
553     String JavaDoc makeLevelMembersSql(DataSource JavaDoc dataSource) {
554
555         // In the case of a virtual cube, if we need to join to the fact
556
// table, we do not necessarily have a single underlying fact table,
557
// as the underlying base cubes in the virtual cube may all reference
558
// different fact tables.
559
//
560
// Therefore, we need to gather the underlying fact tables by going
561
// through the list of measures referenced in the query. And then
562
// we generate one sub-select per fact table, joining against each
563
// underlying fact table, unioning the sub-selects.
564
RolapCube cube = null;
565         boolean virtualCube = false;
566         if (constraint instanceof SqlContextConstraint) {
567             SqlContextConstraint sqlConstraint =
568                 (SqlContextConstraint) constraint;
569             if (sqlConstraint.isJoinRequired()) {
570                 Query query = constraint.getEvaluator().getQuery();
571                 cube = (RolapCube) query.getCube();
572                 virtualCube = cube.isVirtual();
573             }
574         }
575
576         if (virtualCube) {
577             String JavaDoc selectString = "";
578             Query query = constraint.getEvaluator().getQuery();
579             Set JavaDoc<Map<RolapLevel, RolapStar.Column>> baseCubesLevelToColumnMaps =
580                 query.getVirtualCubeBaseCubeMaps();
581             Map<Map<RolapLevel, RolapStar.Column>, RolapMember> measureMap =
582                 query.getLevelMapToMeasureMap();
583
584             // generate sub-selects, each one joining with one of
585
// underlying fact tables
586
int k = -1;
587             for (Map<RolapLevel, RolapStar.Column> map :
588                 baseCubesLevelToColumnMaps)
589             {
590                 boolean finalSelect =
591                     (++k == baseCubesLevelToColumnMaps.size() - 1);
592                 // set the evaluator context so it references a measure
593
// associated with the star that we're currently dealing
594
// with so the sql generated will reference the appropriate
595
// fact table
596
RolapMember measure = measureMap.get(map);
597                 assert measure instanceof RolapStoredMeasure;
598                 Evaluator evaluator = constraint.getEvaluator();
599                 evaluator.push();
600                 evaluator.setContext(measure);
601                 WhichSelect whichSelect =
602                     finalSelect ? WhichSelect.LAST :
603                         WhichSelect.NOT_LAST;
604                 selectString +=
605                     generateSelectForLevels(
606                         dataSource, map, whichSelect);
607                 if (!finalSelect) {
608                     selectString += " union ";
609                 }
610             }
611             return selectString;
612         } else {
613             Map<RolapLevel, RolapStar.Column> map =
614                 cube == null ?
615                     null :
616                     cube.getStar().getLevelToColumnMap(cube);
617             return generateSelectForLevels(dataSource, map, WhichSelect.ONLY);
618         }
619     }
620
621     /**
622      * Generates the SQL string corresponding to the levels referenced.
623      *
624      * @param dataSource jdbc connection that they query will execute
625      * against
626      * @param levelToColumnMap set only in the case of virtual cubes;
627      * provides the appropriate mapping for the base cube being processed
628      * @param whichSelect Position of this select statement in a union
629      * @return SQL statement string
630      */

631     private String JavaDoc generateSelectForLevels(
632         DataSource JavaDoc dataSource,
633         Map<RolapLevel, RolapStar.Column> levelToColumnMap,
634         WhichSelect whichSelect) {
635
636         String JavaDoc s = "while generating query to retrieve members of level(s) " + targets;
637         SqlQuery sqlQuery = SqlQuery.newQuery(dataSource, s);
638
639         // add the selects for all levels to fetch
640
for (Target target : targets) {
641             // if we're going to be enumerating the values for this target,
642
// then we don't need to generate sql for it
643
if (target.srcMembers == null) {
644                 addLevelMemberSql(
645                     sqlQuery,
646                     target.getLevel(),
647                     levelToColumnMap,
648                     whichSelect);
649             }
650         }
651
652         // additional constraints
653
constraint.addConstraint(sqlQuery, levelToColumnMap);
654
655         return sqlQuery.toString();
656     }
657
658     /**
659      * Generates the SQL statement to access members of <code>level</code>. For
660      * example, <blockquote>
661      * <pre>SELECT "country", "state_province", "city"
662      * FROM "customer"
663      * GROUP BY "country", "state_province", "city", "init", "bar"
664      * ORDER BY "country", "state_province", "city"</pre>
665      * </blockquote> accesses the "City" level of the "Customers"
666      * hierarchy. Note that:<ul>
667      *
668      * <li><code>"country", "state_province"</code> are the parent keys;</li>
669      *
670      * <li><code>"city"</code> is the level key;</li>
671      *
672      * <li><code>"init", "bar"</code> are member properties.</li>
673      * </ul>
674      *
675      * @param sqlQuery the query object being constructed
676      * @param level level to be added to the sql query
677      * @param levelToColumnMap set only in the case of virtual cubes;
678      * provides the appropriate mapping for the base cube being processed
679      * @param whichSelect describes whether this select belongs to a larger
680      * select containing unions or this is a non-union select
681      */

682     private void addLevelMemberSql(
683         SqlQuery sqlQuery,
684         RolapLevel level,
685         Map<RolapLevel, RolapStar.Column> levelToColumnMap,
686         WhichSelect whichSelect)
687     {
688         RolapHierarchy hierarchy = level.getHierarchy();
689
690         RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels();
691         int levelDepth = level.getDepth();
692         for (int i = 0; i <= levelDepth; i++) {
693             RolapLevel level2 = levels[i];
694             if (level2.isAll()) {
695                 continue;
696             }
697             hierarchy.addToFrom(sqlQuery, level2.getKeyExp());
698             String JavaDoc keySql = level2.getKeyExp().getExpression(sqlQuery);
699             sqlQuery.addSelect(keySql);
700             sqlQuery.addGroupBy(keySql);
701             hierarchy.addToFrom(sqlQuery, level2.getOrdinalExp());
702
703             constraint.addLevelConstraint(
704                 sqlQuery, null, level2, levelToColumnMap);
705
706             if (level2.hasCaptionColumn()) {
707                 MondrianDef.Expression captionExp = level2.getCaptionExp();
708                 hierarchy.addToFrom(sqlQuery, captionExp);
709                 String JavaDoc captionSql = captionExp.getExpression(sqlQuery);
710                 sqlQuery.addSelect(captionSql);
711                 sqlQuery.addGroupBy(captionSql);
712             }
713
714             String JavaDoc ordinalSql = level2.getOrdinalExp().getExpression(sqlQuery);
715             sqlQuery.addGroupBy(ordinalSql);
716             if (!ordinalSql.equals(keySql)) {
717                 sqlQuery.addSelect(ordinalSql);
718             }
719
720             // If this is a select on a virtual cube, the query will be
721
// a union, so the order by columns need to be numbers,
722
// not column name strings or expressions.
723
switch (whichSelect) {
724             case LAST:
725                 sqlQuery.addOrderBy(
726                     Integer.toString(
727                         sqlQuery.getCurrentSelectListSize()),
728                     true, false, true);
729                 break;
730             case ONLY:
731                 sqlQuery.addOrderBy(ordinalSql, true, false, true);
732                 break;
733             }
734             RolapProperty[] properties = level2.getProperties();
735             for (RolapProperty property : properties) {
736                 String JavaDoc propSql = property.getExp().getExpression(sqlQuery);
737                 sqlQuery.addSelect(propSql);
738                 sqlQuery.addGroupBy(propSql);
739             }
740         }
741     }
742
743     int getMaxRows() {
744         return maxRows;
745     }
746
747     void setMaxRows(int maxRows) {
748         this.maxRows = maxRows;
749     }
750
751     /**
752      * Description of the position of a SELECT statement in a UNION. Queries
753      * on virtual cubes tend to generate unions.
754      */

755     enum WhichSelect {
756         /**
757          * Select statement does not belong to a union.
758          */

759         ONLY,
760         /**
761          * Select statement belongs to a UNION, but is not the last. Typically
762          * this occurs when querying a virtual cube.
763          */

764         NOT_LAST,
765         /**
766          * Select statement is the last in a UNION. Typically
767          * this occurs when querying a virtual cube.
768          */

769         LAST
770     }
771 }
772
773 // End SqlTupleReader.java
774
Popular Tags