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