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