001    /*
002    // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapAggregationManager.java#5 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2001-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2007 Julian Hyde and others
008    // All Rights Reserved.
009    // You must accept the terms of that agreement to use this software.
010    //
011    // jhyde, 30 August, 2001
012    */
013    
014    package mondrian.rolap;
015    
016    import mondrian.rolap.agg.*;
017    import mondrian.olap.*;
018    
019    import java.util.*;
020    import java.io.PrintWriter;
021    
022    /**
023     * <code>RolapAggregationManager</code> manages all {@link
024     * mondrian.rolap.agg.Aggregation}s in the system. It is a singleton class.
025     *
026     * <p> The bits of the implementation which depend upon dimensional concepts
027     * <code>RolapMember</code>, etc.) live in this class, and the other bits live
028     * in the derived class, {@link mondrian.rolap.agg.AggregationManager}.
029     *
030     * @author jhyde
031     * @since 30 August, 2001
032     * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapAggregationManager.java#5 $
033     */
034    public abstract class RolapAggregationManager {
035    
036        protected RolapAggregationManager() {
037        }
038    
039        /**
040         * Creates a request to evaluate the cell identified by
041         * <code>members</code>.
042         *
043         * <p>If any of the members is the null member, returns
044         * null, since there is no cell. If the measure is calculated, returns
045         * null.
046         *
047         * @param members Set of members which constrain the cell
048         * @return Cell request, or null if the requst is unsatisfiable
049         */
050        public static CellRequest makeRequest(final Member[] members)
051        {
052            return makeCellRequest(members, false, false, null);
053        }
054    
055        /**
056         * Creates a request for the fact-table rows underlying the cell identified
057         * by <code>members</code>.
058         *
059         * <p>If any of the members is the null member, returns null, since there
060         * is no cell. If the measure is calculated, returns null.
061         *
062         * @param members           Set of members which constrain the cell
063         *
064         * @param extendedContext   If true, add non-constraining columns to the
065         *                          query for levels below each current member.
066         *                          This additional context makes the drill-through
067         *                          queries easier for humans to understand.
068         *
069         * @param cube              Cube
070         * @return Cell request, or null if the requst is unsatisfiable
071         */
072        public static CellRequest makeDrillThroughRequest(
073            final Member[] members,
074            final boolean extendedContext,
075            RolapCube cube)
076        {
077            assert cube != null;
078            return makeCellRequest(members, true, extendedContext, cube);
079        }
080    
081        /**
082         * Creates a request to evaluate the cell identified by the context specified
083         * in <code>evaluator</code>.
084         *
085         * <p>If any of the members from the context is the null member, returns
086         * null, since there is no cell. If the measure is calculated, returns
087         * null.
088         *
089         * @param evaluator the cell specified by the evaluator context
090         * @return Cell request, or null if the requst is unsatisfiable
091         */
092        public static CellRequest makeRequest(
093            RolapEvaluator evaluator) {
094            final Member[] currentMembers = evaluator.getMembers();
095            final List<List<RolapMember>> aggregationLists =
096                evaluator.getAggregationLists();
097    
098            final RolapStoredMeasure measure = 
099                (RolapStoredMeasure) currentMembers[0];
100            final RolapStar.Measure starMeasure =
101                (RolapStar.Measure) measure.getStarMeasure();
102            assert starMeasure != null;
103            int starColumnCount = starMeasure.getStar().getColumnCount();
104    
105            CellRequest request =
106                makeCellRequest(currentMembers, false, false, null);
107    
108            /*
109             * Now setting the compound keys.
110             * First find out the columns referenced in the aggregateMemberList.
111             * Each list defines a compound member.
112             */
113            if (aggregationLists == null) {
114                return request;
115            }
116    
117            BitKey compoundBitKey;
118            StarPredicate compoundPredicate;
119            Map<BitKey, List<RolapCubeMember[]>> compoundGroupMap;
120            boolean unsatisfiable;
121    
122            /*
123             * For each aggregationList, generate the optimal form of compoundPredicate.
124             * These compoundPredicates are AND'ed together when sql is generated for
125             * them.
126             */
127            for (List<RolapMember> aggregationList : aggregationLists) {
128                compoundBitKey = BitKey.Factory.makeBitKey(starColumnCount);
129                compoundBitKey.clear();
130                compoundGroupMap = new LinkedHashMap<BitKey, List<RolapCubeMember[]>>();
131                
132                // Go through the compound members(tuples) once and separete them
133                // into groups.
134                unsatisfiable =
135                    makeCompoundGroup(
136                        starColumnCount, 
137                        measure.getCube(), 
138                        aggregationList, 
139                        compoundGroupMap);
140                
141                if (unsatisfiable) {
142                    return null;
143                }
144                compoundPredicate =
145                    makeCompoundPredicate(compoundGroupMap, measure.getCube());
146                
147                if (compoundPredicate != null) {
148                    /*
149                     * Only add the compound constraint when it is not empty.
150                     */
151                    for (BitKey bitKey : compoundGroupMap.keySet()) {
152                        compoundBitKey = compoundBitKey.or(bitKey);
153                    }
154                    request.addAggregateList(compoundBitKey, compoundPredicate);
155                }
156            }
157    
158            return request;
159        }
160    
161        private static CellRequest makeCellRequest(
162            final Member[] members,
163            boolean drillThrough,
164            final boolean extendedContext,
165            RolapCube cube)
166        {
167            // Need cube for drill-through requests
168            assert drillThrough == (cube != null);
169    
170            if (extendedContext) {
171                assert (drillThrough);
172            }
173    
174            final RolapStoredMeasure measure;
175            if (drillThrough) {
176                cube = RolapCell.chooseDrillThroughCube(members, cube);
177                if (cube == null) {
178                    return null;
179                }
180                if (members[0] instanceof RolapStoredMeasure) {
181                    measure = (RolapStoredMeasure) members[0];
182                } else {
183                    measure = (RolapStoredMeasure) cube.getMeasures()[0];
184                }
185            } else {
186                if (members[0] instanceof RolapStoredMeasure) {
187                    measure = (RolapStoredMeasure) members[0];
188                } else {
189                    return null;
190                }
191            }
192    
193            final RolapStar.Measure starMeasure =
194                (RolapStar.Measure) measure.getStarMeasure();
195            assert starMeasure != null;
196            final CellRequest request =
197                new CellRequest(starMeasure, extendedContext, drillThrough);
198    
199            // Since 'request.extendedContext == false' is a well-worn code path,
200            // we have moved the test outside the loop.
201            if (extendedContext) {
202                for (int i = 1; i < members.length; i++) {
203                    final RolapCubeMember member = (RolapCubeMember) members[i];
204                    addNonConstrainingColumns(member, measure.getCube(), request);
205    
206                    final RolapCubeLevel level = member.getLevel();
207                    final boolean needToReturnNull =
208                        level.getLevelReader().constrainRequest(
209                            member, measure.getCube(), request);
210                    if (needToReturnNull) {
211                        return null;
212                    }
213                }
214            } else {
215                for (int i = 1; i < members.length; i++) {
216                    RolapCubeMember member = (RolapCubeMember) members[i];
217                    final RolapCubeLevel level = member.getLevel();
218                    final boolean needToReturnNull =
219                        level.getLevelReader().constrainRequest(
220                            member, measure.getCube(), request);
221                    if (needToReturnNull) {
222                        return null;
223                    }
224                }
225            }
226            return request;
227        }
228    
229        /**
230         * Adds the key columns as non-constraining columns. For
231         * example, if they asked for [Gender].[M], [Store].[USA].[CA]
232         * then the following levels are in play:<ul>
233         *   <li>Gender = 'M'
234         *   <li>Marital Status not constraining
235         *   <li>Nation = 'USA'
236         *   <li>State = 'CA'
237         *   <li>City not constraining
238         * </ul>
239         *
240         * <p>Note that [Marital Status] column is present by virtue of
241         * the implicit [Marital Status].[All] member. Hence the SQL
242         *
243         *   <blockquote><pre>
244         *   select [Marital Status], [City]
245         *   from [Star]
246         *   where [Gender] = 'M'
247         *   and [Nation] = 'USA'
248         *   and [State] = 'CA'
249         *   </pre></blockquote>
250         *
251         * @param member Member to constraint
252         * @param baseCube base cube if virtual
253         * @param request Cell request
254         */
255        private static void addNonConstrainingColumns(
256            final RolapCubeMember member,
257            final RolapCube baseCube,
258            final CellRequest request)
259        {
260            final RolapCubeHierarchy hierarchy = member.getHierarchy();
261            final Level[] levels = hierarchy.getLevels();
262            for (int j = levels.length - 1, depth = member.getLevel().getDepth();
263                 j > depth; j--) {
264                final RolapCubeLevel level = (RolapCubeLevel)levels[j];
265                RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
266                if (column != null) {
267                    request.addConstrainedColumn(column, null);
268                    if (request.extendedContext &&
269                            level.getNameExp() != null) {
270                        final RolapStar.Column nameColumn = column.getNameColumn();
271                        Util.assertTrue(nameColumn != null);
272                        request.addConstrainedColumn(nameColumn, null);
273                    }
274                }
275            }
276        }
277    
278        /*
279         * Group members(or tuples) from the same compound(i.e. hierarchy) into groups 
280         * that are constrained by the same set of columns.
281         *
282         * E.g.
283         *
284         * Members
285         *     [USA].[CA],
286         *     [Canada].[BC],
287         *     [USA].[CA].[San Francisco],
288         *     [USA].[OR].[Portland]
289         *
290         * will be grouped into
291         * Group 1: 
292         *     {[USA].[CA], [Canada].[BC]}
293         * Group 2: 
294         *     {[USA].[CA].[San Francisco], [USA].[OR].[Portland]}
295         *
296         * This helps with generating optimal form of sql.
297         *
298         * In case of aggregating over a list of tuples, similar logic also 
299         * applies.
300         * 
301         * For example:
302         * Tuples:
303         *     ([Gender].[M], [Store].[All Stores].[USA].[CA])
304         *     ([Gender].[F], [Store].[All Stores].[USA].[CA])
305         *     ([Gender].[M], [Store].[All Stores].[USA])
306         *     ([Gender].[F], [Store].[All Stores].[Canada])
307         *
308         * will be grouped into
309         * Group 1: 
310         *     {([Gender].[M], [Store].[All Stores].[USA].[CA]),
311         *      ([Gender].[F], [Store].[All Stores].[USA].[CA])}
312         * Group 2: 
313         *     {([Gender].[M], [Store].[All Stores].[USA]),
314         *      ([Gender].[F], [Store].[All Stores].[Canada])}
315         * 
316         * This function returns a boolean value indicating if any constraint
317         * can be created from the aggregationList. It is possible that only part
318         * of the aggregationList can be applied, which still leads to a (partial)
319         * constraint that is represented by the compoundGroupMap.
320         */
321        private static boolean makeCompoundGroup(
322            int starColumnCount,
323            RolapCube baseCube,
324            List aggregationList,
325            Map<BitKey, List<RolapCubeMember[]>> compoundGroupMap)
326        {
327            // The more generalized aggregation as aggregating over tuples.
328            // The special case is a tuple defined by only one member.
329            int unsatisfiableTupleCount=0;
330            for (Object aggregation : aggregationList) {
331                boolean isTuple;
332                if (aggregation instanceof Member) {
333                    isTuple = false;
334                } else if (aggregation instanceof Member[] &&
335                           ((Member[])aggregation).length > 0 &&
336                           ((Member[])aggregation)[0] instanceof RolapCubeMember) {
337                    isTuple = true;
338                } else {
339                    unsatisfiableTupleCount ++;
340                    continue;          
341                }
342                
343                BitKey bitKey = BitKey.Factory.makeBitKey(starColumnCount);
344                RolapCubeMember[] tuple;
345                
346                if (!isTuple) {
347                    tuple = new RolapCubeMember[]{(RolapCubeMember)aggregation};
348                } else {
349                    tuple = new RolapCubeMember[((Member[])aggregation).length];
350                    int i = 0;
351                    for (Member member : (Member[])aggregation) {
352                        tuple[i] = (RolapCubeMember)member;
353                        i++;
354                    }
355                }
356                
357                boolean tupleUnsatisfiable = false;
358                for (RolapCubeMember member : tuple) {
359                    // Tuple cannot be constrained if any of the member cannot be.
360                    tupleUnsatisfiable =
361                        makeCompoundGroupForMember(member, baseCube, bitKey);
362                    if (tupleUnsatisfiable) {
363                        // If this tuple is unsatisfiable, skip it and try to 
364                        // constrain the next tuple.
365                        unsatisfiableTupleCount ++;
366                        break;
367                    }
368                }
369    
370                if (!tupleUnsatisfiable && !bitKey.isEmpty()) {
371                    // Found tuple(columns) to constrain, 
372                    // now add it to the compoundGroupMap
373                    addTupleToCompoundGroupMap(tuple, bitKey, compoundGroupMap);
374                }
375            }
376            
377            return (unsatisfiableTupleCount == aggregationList.size());
378        }
379    
380        private static void addTupleToCompoundGroupMap(
381            RolapCubeMember[] tuple,
382            BitKey bitKey,
383            Map<BitKey, List<RolapCubeMember[]>> compoundGroupMap)
384        {
385            List<RolapCubeMember[]> compoundGroup = compoundGroupMap.get(bitKey);
386            if (compoundGroup == null) {
387                compoundGroup = new ArrayList<RolapCubeMember[]>();
388                compoundGroupMap.put(bitKey, compoundGroup);
389            }
390            compoundGroup.add(tuple);
391    
392        }
393    
394        private static boolean makeCompoundGroupForMember(
395            RolapCubeMember member,
396            RolapCube baseCube,
397            BitKey bitKey)
398        {
399            RolapCubeMember levelMember = member;
400            boolean memberUnsatisfiable = false;
401            while (levelMember != null) {
402                RolapCubeLevel level = levelMember.getLevel();
403                // Only need to constrain the nonAll levels
404                if (!level.isAll()) {
405                    RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
406                    if (column != null) {
407                        bitKey.set(column.getBitPosition());
408                    } else {
409                        // One level in a member causes the member to be 
410                        // unsatisfiable.
411                        memberUnsatisfiable = true;
412                        break;
413                    }
414                }
415    
416                levelMember = levelMember.getParentMember();
417            }
418            return memberUnsatisfiable;
419        }
420    
421        /**
422         * Translate Map<BitKey, List<RolapMember>> of the same compound member into
423         * ListPredicate by traversing list of members or tuples.
424         * <p>1. The example below is for list of tuples
425         *
426         * <blockquote>
427         * <p>group 1: [Gender].[M], [Store].[All Stores].[USA].[CA]
428         * group 2: [Gender].[F], [Store].[All Stores].[USA].[CA]
429         * </blockquote>
430         * is translated into
431         * <blockquote>
432         * <p>(Gender=M AND Store_State=CA AND Store_Country=USA)
433         * OR
434         * (Gender=F AND Store_State=CA AND Store_Country=USA)
435         * </blockquote>
436         * <p>The caller of this method will translate this representation into
437         * appropriate SQL form as
438         * <blockquote>
439         *  <p>where (gender = 'M' and Store_State = 'CA' AND Store_Country = 'USA')
440         *     OR (Gender = 'F' and Store_State = 'CA' AND Store_Country = 'USA')
441         * </blockquote>
442         * <p>2. The example below for a list of members
443         * <blockquote>
444         * <p>group 1: [USA].[CA], [Canada].[BC]
445         * group 2: [USA].[CA].[San Francisco], [USA].[OR].[Portland]
446         * </blockquote>
447         * is translated into:
448         * <blockquote>
449         * <p>(Country=USA AND State=CA)
450         *     OR (Country=Canada AND State=BC)
451         * OR
452         * (Country=USA AND State=CA AND City=San Francisco)
453         *     OR (Country=USA AND State=OR AND City=Portland)
454         * </blockquote>
455         * <p>The caller of this method will translate this representation into
456         * appropriate SQL form. For exmaple, if the underlying DB supports multi value
457         * IN-list, the second group will turn into this predicate:
458         * <blockquote>
459         * <p>    where (country, state, city) IN ((USA, CA, San Francisco),
460         *                                      (USA, OR, Portland))
461         * </blockquote>
462         * or, if the DB does not support multi-value IN list:
463         * <blockquote>
464         * <p>    where country=USA AND
465         *           ((state=CA AND city = San Francisco) OR
466         *            (state=OR AND city=Portland))
467         * </blockquote>
468         *
469         * @param compoundGroupMap
470         * @param baseCube base cube if virtual 
471         * @return compound predicate for a tuple or a member
472         */
473        private static StarPredicate makeCompoundPredicate(
474            Map<BitKey, List<RolapCubeMember[]>> compoundGroupMap,
475            RolapCube baseCube)
476        {
477            List<StarPredicate> compoundPredicateList =
478                new ArrayList<StarPredicate> ();
479            for (List<RolapCubeMember[]> group : compoundGroupMap.values()) {
480                /*
481                 * e.g. 
482                 * {[USA].[CA], [Canada].[BC]}
483                 * or
484                 * {
485                 */
486                StarPredicate compoundGroupPredicate = null;
487                for (RolapCubeMember[] tuple : group) {
488                   /*
489                    * [USA].[CA]
490                    */                
491                    StarPredicate tuplePredicate = null;
492    
493                    for (RolapCubeMember member : tuple) {
494                    tuplePredicate = makeCompoundPredicateForMember(
495                            member, baseCube, tuplePredicate);
496                    }
497                    if (tuplePredicate != null) {
498                        if (compoundGroupPredicate == null) {
499                            compoundGroupPredicate = tuplePredicate;
500                        } else {
501                            compoundGroupPredicate =
502                                compoundGroupPredicate.or(tuplePredicate);
503                        }
504                    }
505                }
506                
507                if (compoundGroupPredicate != null) {
508                    /*
509                     * Sometimes the compound member list does not constrain any
510                     * columns; for example, if only AllLevel is present.
511                     */
512                    compoundPredicateList.add(compoundGroupPredicate);
513                }
514            }
515            
516            StarPredicate compoundPredicate = null;
517    
518            if (compoundPredicateList.size() > 1) {
519                compoundPredicate = new OrPredicate(compoundPredicateList);
520            } else if (compoundPredicateList.size() == 1) {
521                compoundPredicate = compoundPredicateList.get(0);
522            }
523    
524            return compoundPredicate;
525        }
526    
527        private static StarPredicate makeCompoundPredicateForMember(
528            RolapCubeMember member,
529            RolapCube baseCube,
530            StarPredicate memberPredicate)
531        {
532            while (member != null) {
533                RolapCubeLevel level = member.getLevel();
534                if (!level.isAll()) {
535    
536                    RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
537                    if (memberPredicate == null) {
538                        memberPredicate =
539                            new ValueColumnPredicate(column, member.getKey());
540                    } else {
541                        memberPredicate =
542                            memberPredicate.and(
543                                new ValueColumnPredicate(column, member.getKey()));
544                    }
545                }
546                // Don't need to constrain USA if CA is unique
547                if (member.getLevel().isUnique()) {
548                    break;
549                }
550                member = member.getParentMember();
551            }
552            return memberPredicate;
553        }
554    
555        /**
556         * Retrieves the value of a cell from the cache.
557         *
558         * @param request Cell request
559         * @pre request != null && !request.isUnsatisfiable()
560         * @return Cell value, or null if cell is not in any aggregation in cache,
561         *   or {@link Util#nullValue} if cell's value is null
562         */
563        public abstract Object getCellFromCache(CellRequest request);
564    
565        public abstract Object getCellFromCache(
566            CellRequest request,
567            PinSet pinSet);
568    
569        /**
570         * Generates a SQL statement which will return the rows which contribute to
571         * this request.
572         *
573         * @param request Cell request
574         * @param countOnly If true, return a statment which returns only the count
575         * @return SQL statement
576         */
577        public abstract String getDrillThroughSql(
578            CellRequest request,
579            boolean countOnly);
580    
581        /**
582         * Returns an API with which to explicitly manage the contents of the cache.
583         *
584         * @param pw Print writer, for tracing
585         * @return CacheControl API
586         */
587        public CacheControl getCacheControl(final PrintWriter pw) {
588            return new CacheControlImpl() {
589                protected void flushNonUnion(final CellRegion region) {
590                    final List<RolapStar> starList = getStarList(region);
591    
592                    // For each of the candidate stars, scan the list of aggregates.
593                    for (RolapStar star : starList) {
594                        star.flush(this, region);
595                    }
596                }
597    
598                public void flush(final CellRegion region) {
599                    if (pw != null) {
600                        pw.println("Cache state before flush:");
601                        printCacheState(pw, region);
602                        pw.println();
603                    }
604                    super.flush(region);
605                    if (pw != null) {
606                        pw.println("Cache state after flush:");
607                        printCacheState(pw, region);
608                        pw.println();
609                    }
610                }
611    
612                public void trace(final String message) {
613                    if (pw != null) {
614                        pw.println(message);
615                    }
616                }
617            };
618        }
619    
620        public static RolapCacheRegion makeCacheRegion(
621            final RolapStar star,
622            final CacheControl.CellRegion region)
623        {
624            final List<Member> measureList = CacheControlImpl.findMeasures(region);
625            final List<RolapStar.Measure> starMeasureList =
626                new ArrayList<RolapStar.Measure>();
627            RolapCube baseCube = null;
628            for (Member measure : measureList) {
629                if (!(measure instanceof RolapStoredMeasure)) {
630                    continue;
631                }
632                final RolapStoredMeasure storedMeasure =
633                    (RolapStoredMeasure) measure;
634                final RolapStar.Measure starMeasure =
635                    (RolapStar.Measure) storedMeasure.getStarMeasure();
636                assert starMeasure != null;
637                if (star != starMeasure.getStar()) {
638                    continue;
639                }
640                // TODO: each time this code executes, baseCube is set.
641                // Should there be a 'break' here? Are all of the
642                // storedMeasure cubes the same cube? Is the measureList always
643                // non-empty so that baseCube is always set?
644                baseCube = storedMeasure.getCube();
645                starMeasureList.add(starMeasure);
646            }
647            final RolapCacheRegion cacheRegion =
648                new RolapCacheRegion(star, starMeasureList);
649            if (region instanceof CacheControlImpl.CrossjoinCellRegion) {
650                final CacheControlImpl.CrossjoinCellRegion crossjoin =
651                    (CacheControlImpl.CrossjoinCellRegion) region;
652                for (CacheControl.CellRegion component : crossjoin.getComponents()) {
653                    constrainCacheRegion(cacheRegion, baseCube, component);
654                }
655            } else {
656                constrainCacheRegion(cacheRegion, baseCube, region);
657            }
658            return cacheRegion;
659        }
660    
661        private static void constrainCacheRegion(
662            final RolapCacheRegion cacheRegion,
663            final RolapCube baseCube,
664            final CacheControl.CellRegion region)
665        {
666            if (region instanceof CacheControlImpl.MemberCellRegion) {
667                final CacheControlImpl.MemberCellRegion memberCellRegion =
668                    (CacheControlImpl.MemberCellRegion) region;
669                final List<Member> memberList = memberCellRegion.getMemberList();
670                for (Member member : memberList) {
671                    if (member.isMeasure()) {
672                        continue;
673                    }
674                    final RolapCubeMember rolapMember = (RolapCubeMember) member;
675                    final RolapCubeLevel level = rolapMember.getLevel();
676                    RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
677                    
678                    level.getLevelReader().constrainRegion(
679                        new MemberColumnPredicate(column, rolapMember), 
680                        baseCube, 
681                        cacheRegion);
682                }
683            } else if (region instanceof CacheControlImpl.MemberRangeCellRegion) {
684                final CacheControlImpl.MemberRangeCellRegion rangeRegion =
685                    (CacheControlImpl.MemberRangeCellRegion) region;
686                final RolapCubeLevel level = (RolapCubeLevel)rangeRegion.getLevel();
687                RolapStar.Column column = level.getBaseStarKeyColumn(baseCube);
688                
689                level.getLevelReader().constrainRegion(
690                    new RangeColumnPredicate(
691                        column,
692                        rangeRegion.getLowerInclusive(),
693                        (rangeRegion.getLowerBound() == null ?
694                            null :
695                            new MemberColumnPredicate(
696                                column, rangeRegion.getLowerBound())),
697                        rangeRegion.getUpperInclusive(),
698                        (rangeRegion.getUpperBound() == null ?
699                            null :
700                            new MemberColumnPredicate(
701                                column, rangeRegion.getUpperBound()))),
702                    baseCube, 
703                    cacheRegion);
704            } else {
705                throw new UnsupportedOperationException();
706            }
707        }
708    
709        /**
710         * Returns a {@link mondrian.rolap.CellReader} which reads cells from cache.
711         */
712        public CellReader getCacheCellReader() {
713            return new CellReader() {
714                // implement CellReader
715                public Object get(RolapEvaluator evaluator) {
716                    CellRequest request = makeRequest(evaluator);
717                    if (request == null || request.isUnsatisfiable()) {
718                        // request out of bounds
719                        return Util.nullValue;
720                    }
721                    return getCellFromCache(request);
722                }
723    
724                public int getMissCount() {
725                    return 0; // RolapAggregationManager never lies
726                }
727                
728                public boolean isDirty() {
729                    return false;
730                }
731            };
732        }
733    
734        /**
735         * Creates a {@link PinSet}.
736         *
737         * @return a new PinSet
738         */
739        public abstract PinSet createPinSet();
740    
741        /**
742         * A set of segments which are pinned for a short duration as a result of a
743         * cache inquiry.
744         */
745        public interface PinSet {}
746    }
747    
748    // End RolapAggregationManager.java