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