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