001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/agg/Segment.java#51 $ 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) 2002-2002 Kana Software, Inc. 007 // Copyright (C) 2002-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, 21 March, 2002 012 */ 013 package mondrian.rolap.agg; 014 015 import mondrian.olap.*; 016 import mondrian.rolap.*; 017 018 import java.sql.*; 019 import java.util.*; 020 import java.io.PrintWriter; 021 022 import org.apache.log4j.Logger; 023 024 /** 025 * A <code>Segment</code> is a collection of cell values parameterized by 026 * a measure, and a set of (column, value) pairs. An example of a segment is</p> 027 * 028 * <blockquote> 029 * <p>(Unit sales, Gender = 'F', State in {'CA','OR'}, Marital Status = <i> 030 * anything</i>)</p> 031 * </blockquote> 032 * 033 * <p>All segments over the same set of columns belong to an Aggregation, in this 034 * case</p> 035 * 036 * <blockquote> 037 * <p>('Sales' Star, Gender, State, Marital Status)</p> 038 * </blockquote> 039 * 040 * <p>Note that different measures (in the same Star) occupy the same Aggregation. 041 * Aggregations belong to the AggregationManager, a singleton.</p> 042 * <p>Segments are pinned during the evaluation of a single MDX query. The query 043 * evaluates the expressions twice. The first pass, it finds which cell values it 044 * needs, pins the segments containing the ones which are already present (one 045 * pin-count for each cell value used), and builds a {@link CellRequest 046 * cell request} for those which are not present. It executes 047 * the cell request to bring the required cell values into the cache, again, 048 * pinned. Then it evalutes the query a second time, knowing that all cell values 049 * are available. Finally, it releases the pins.</p> 050 * 051 * <p>A Segment may have a list of excluded {@link Region} objects. These are 052 * caused by cache flushing. Usually a segment is a hypercube: it is defined by 053 * a set of values on each of its axes. But after a cache flush request, a 054 * segment may have a rectangular 'hole', and therefore not be a hypercube 055 * anymore. 056 * 057 * <p>For example, the segment defined by {CA, OR, WA} * {F, M} is a 058 * 2-dimensional hyper-rectangle with 6 cells. After flushing {CA, OR, TX} * 059 * {F}, the result is 4 cells: 060 * 061 * <pre> 062 * F M 063 * CA out in 064 * OR out in 065 * WA in in 066 * </pre> 067 * 068 * defined by the original segment minus the region ({CA, OR} * {F}). 069 * 070 * @author jhyde 071 * @since 21 March, 2002 072 * @version $Id: //open/mondrian/src/main/mondrian/rolap/agg/Segment.java#51 $ 073 */ 074 class Segment { 075 private static int nextId = 0; // generator for "id" 076 077 final int id; // for debug 078 private String desc; 079 final Aggregation aggregation; 080 final RolapStar.Measure measure; 081 082 final Aggregation.Axis[] axes; 083 private SegmentDataset data; 084 private final CellKey cellKey; // workspace 085 086 /** State of the segment (loading, ready, etc.). */ 087 private State state; 088 089 /** 090 * List of regions to ignore when reading this segment. This list is 091 * populated when a region is flushed. The cells for these regions may be 092 * physically in the segment, because trimming segments can be expensive, 093 * but should still be ignored. 094 */ 095 private final List<Region> excludedRegions; 096 private static final Logger LOGGER = 097 Logger.getLogger(Segment.class); 098 099 /** 100 * Creates a <code>Segment</code>; it's not loaded yet. 101 * 102 * @param aggregation The aggregation this <code>Segment</code> belongs to 103 * @param measure Measure whose values this Segment contains 104 * @param axes List of axes; each is a constraint plus a list of values 105 * @param excludedRegions List of regions which are not in this segment. 106 */ 107 Segment( 108 Aggregation aggregation, 109 RolapStar.Measure measure, 110 Aggregation.Axis[] axes, 111 List<Region> excludedRegions) 112 { 113 this.id = nextId++; 114 this.aggregation = aggregation; 115 this.measure = measure; 116 this.axes = axes; 117 this.cellKey = CellKey.Generator.newCellKey(axes.length); 118 this.state = State.Loading; 119 this.excludedRegions = excludedRegions; 120 for (Region region : excludedRegions) { 121 assert region.getPredicates().size() == axes.length; 122 } 123 } 124 125 /** 126 * Sets the data, and notifies any threads which are blocked in 127 * {@link #waitUntilLoaded}. 128 */ 129 synchronized void setData( 130 SegmentDataset data, 131 RolapAggregationManager.PinSet pinnedSegments) { 132 Util.assertTrue(this.data == null); 133 Util.assertTrue(this.state == State.Loading); 134 135 this.data = data; 136 this.state = State.Ready; 137 138 notifyAll(); 139 } 140 141 /** 142 * If this segment is still loading, signals that it failed to load, and 143 * notifies any threads which are blocked in {@link #waitUntilLoaded}. 144 */ 145 synchronized void setFailIfStillLoading() { 146 switch (state) { 147 case Loading: 148 Util.assertTrue(this.data == null); 149 this.state = State.Failed; 150 notifyAll(); 151 break; 152 case Ready: 153 // The segment loaded just fine. 154 break; 155 default: 156 throw Util.badValue(state); 157 } 158 } 159 160 public boolean isReady() { 161 return (state == State.Ready); 162 } 163 164 boolean isFailed() { 165 return (state == State.Failed); 166 } 167 168 private void makeDescription(StringBuilder buf, boolean values) { 169 final String sep = Util.nl + " "; 170 buf.append(printSegmentHeaderInfo(sep)); 171 172 RolapStar.Column[] columns = aggregation.getColumns(); 173 for (int i = 0; i < columns.length; i++) { 174 buf.append(sep); 175 buf.append(columns[i].getExpression().getGenericExpression()); 176 final Aggregation.Axis axis = axes[i]; 177 axis.getPredicate().describe(buf); 178 if (values && isReady()) { 179 Object[] keys = axis.getKeys(); 180 buf.append(", values={"); 181 for (int j = 0; j < keys.length; j++) { 182 if (j > 0) { 183 buf.append(", "); 184 } 185 Object key = keys[j]; 186 buf.append(key); 187 } 188 buf.append("}"); 189 } 190 } 191 if (!excludedRegions.isEmpty()) { 192 buf.append(sep); 193 buf.append("excluded={"); 194 int k = 0; 195 for (Region excludedRegion : excludedRegions) { 196 if (k++ > 0) { 197 buf.append(", "); 198 } 199 excludedRegion.describe(buf); 200 } 201 buf.append('}'); 202 } 203 buf.append('}'); 204 } 205 206 private String printSegmentHeaderInfo(String sep) { 207 StringBuilder buf = new StringBuilder(); 208 buf.append("Segment #"); 209 buf.append(id); 210 buf.append(" {"); 211 buf.append(sep); 212 buf.append("measure="); 213 buf.append(measure.getAggregator().getExpression( 214 measure.getExpression().getGenericExpression())); 215 return buf.toString(); 216 } 217 218 public String toString() { 219 if (this.desc == null) { 220 StringBuilder buf = new StringBuilder(64); 221 makeDescription(buf, false); 222 this.desc = buf.toString(); 223 } 224 return this.desc; 225 } 226 227 /** 228 * Retrieves the value at the location identified by 229 * <code>keys</code>. 230 * 231 * <p>Returns<ul> 232 * 233 * <li>{@link Util#nullValue} if the cell value 234 * is null (because no fact table rows met those criteria);</li> 235 * 236 * <li><code>null</code> if the value is not supposed to be in this segment 237 * (because one or more of the keys do not pass the axis criteria);</li> 238 * 239 * <li>the data value otherwise</li> 240 * 241 * </ul></p> 242 * 243 */ 244 synchronized Object getCellValue(Object[] keys) { 245 assert keys.length == axes.length; 246 int missed = 0; 247 for (int i = 0; i < keys.length; i++) { 248 Object key = keys[i]; 249 int offset = axes[i].getOffset(key); 250 if (offset < 0) { 251 if (axes[i].getPredicate().evaluate(key)) { 252 // see whether this segment should contain this value 253 missed++; 254 continue; 255 } else { 256 // this value should not appear in this segment; we 257 // should be looking in a different segment 258 return null; 259 } 260 } 261 cellKey.setAxis(i, offset); 262 } 263 if (isExcluded(keys)) { 264 // this value should not appear in this segment; we 265 // should be looking in a different segment 266 return null; 267 } 268 if (missed > 0) { 269 // the value should be in this segment, but isn't, because one 270 // or more of its keys does have any values 271 return Util.nullValue; 272 } else { 273 Object o = data.get(cellKey); 274 if (o == null) { 275 o = Util.nullValue; 276 } 277 return o; 278 } 279 } 280 281 /** 282 * Returns whether the given set of key values will be in this segment 283 * when it finishes loading. 284 */ 285 boolean wouldContain(Object[] keys) { 286 Util.assertTrue(keys.length == axes.length); 287 for (int i = 0; i < keys.length; i++) { 288 Object key = keys[i]; 289 if (!axes[i].getPredicate().evaluate(key)) { 290 return false; 291 } 292 } 293 return !isExcluded(keys); 294 } 295 296 /** 297 * Returns whether a cell value is excluded from this segment. 298 */ 299 private boolean isExcluded(Object[] keys) { 300 for (Region excludedRegion : excludedRegions) { 301 if (excludedRegion.wouldContain(keys)) { 302 return true; 303 } 304 } 305 return false; 306 } 307 308 /** 309 * Blocks until this segment has finished loading; if this segment has 310 * already loaded, returns immediately. 311 */ 312 public synchronized void waitUntilLoaded() { 313 if (isLoading()) { 314 try { 315 LOGGER.debug("Waiting on " + printSegmentHeaderInfo(",")); 316 wait(); 317 } catch (InterruptedException e) { 318 } 319 switch (state) { 320 case Ready: 321 return; // excellent! 322 case Failed: 323 throw Util.newError("Pending segment failed to load: " 324 + toString()); 325 default: 326 throw Util.badValue(state); 327 } 328 } 329 } 330 331 private boolean isLoading() { 332 return state == State.Loading; 333 } 334 335 /** 336 * Prints the state of this <code>Segment</code>, including constraints 337 * and values. Blocks the current thread until the segment is loaded. 338 * 339 * @param pw Writer 340 */ 341 public void print(PrintWriter pw) { 342 waitUntilLoaded(); 343 final StringBuilder buf = new StringBuilder(); 344 makeDescription(buf, true); 345 pw.print(buf.toString()); 346 pw.println(); 347 } 348 349 public List<Region> getExcludedRegions() { 350 return excludedRegions; 351 } 352 353 /** 354 * Returns the number of cells in this Segment, deducting cells in 355 * excluded regions. 356 * 357 * <p>This method may return a value which is slightly too low, or 358 * occasionally even negative. This occurs when a Segment has more than one 359 * excluded region, and those regions overlap. Cells which are in both 360 * regions will be counted twice. 361 * 362 * @return Number of cells in this Segment 363 */ 364 public int getCellCount() { 365 int cellCount = 1; 366 for (Aggregation.Axis axis : axes) { 367 cellCount *= axis.getKeys().length; 368 } 369 for (Region excludedRegion : excludedRegions) { 370 cellCount -= excludedRegion.cellCount; 371 } 372 return cellCount; 373 } 374 375 /** 376 * Creates a Segment which has the same dimensionality as this Segment and a 377 * subset of the values. 378 * 379 * <p>If <code>bestColumn</code> is not -1, the <code>bestColumn</code>th 380 * column's predicate should be replaced by <code>bestPredicate</code>. 381 * 382 * @param axisKeepBitSets For each axis, a bitmap of the axis values to 383 * keep; each axis must have at least one bit set 384 * @param bestColumn 385 * @param bestPredicate 386 * @param excludedRegions List of regions to exclude from segment 387 * @return Segment containing a subset of the values 388 */ 389 Segment createSubSegment( 390 BitSet[] axisKeepBitSets, 391 int bestColumn, 392 StarColumnPredicate bestPredicate, 393 List<Segment.Region> excludedRegions) { 394 assert axisKeepBitSets.length == axes.length; 395 396 // Create a new segment with a subset of the values. If only one 397 // of the axes is restricted, restrict just that axis. If more than 398 // one of the axis is restricted, add a negation to the segment. 399 final Aggregation.Axis[] newAxes = axes.clone(); 400 401 // For each axis, map from old position to new position. 402 final Map<Integer,Integer>[] axisPosMaps = new Map[axes.length]; 403 404 int valueCount = 1; 405 for (int j = 0; j < axes.length; j++) { 406 Aggregation.Axis axis = axes[j]; 407 StarColumnPredicate newPredicate = axis.getPredicate(); 408 if (j == bestColumn) { 409 newPredicate = bestPredicate; 410 } 411 final Comparable<?>[] axisKeys = axis.getKeys(); 412 BitSet keepBitSet = axisKeepBitSets[j]; 413 int firstClearBit = keepBitSet.nextClearBit(0); 414 Comparable<?>[] newAxisKeys; 415 if (firstClearBit >= axisKeys.length) { 416 // Keep everything 417 newAxisKeys = axisKeys; 418 axisPosMaps[j] = null; // identity map 419 } else { 420 List<Object> newAxisKeyList = new ArrayList<Object>(); 421 Map<Integer, Integer> map 422 = axisPosMaps[j] 423 = new HashMap<Integer, Integer>(); 424 for (int bit = keepBitSet.nextSetBit(0); 425 bit >= 0; 426 bit = keepBitSet.nextSetBit(bit + 1)) { 427 map.put(bit, newAxisKeyList.size()); 428 newAxisKeyList.add(axisKeys[bit]); 429 } 430 newAxisKeys = 431 newAxisKeyList.toArray( 432 new Comparable<?>[newAxisKeyList.size()]); 433 assert newAxisKeys.length > 0; 434 } 435 final Aggregation.Axis newAxis = 436 new Aggregation.Axis(newPredicate, newAxisKeys); 437 newAxes[j] = newAxis; 438 valueCount *= newAxisKeys.length; 439 } 440 441 // Create a new segment. 442 final Segment newSegment = 443 new Segment(aggregation, measure, newAxes, excludedRegions); 444 445 // Create a dataset containing a subset of the current dataset. 446 // Keep the same representation as the current dataset. 447 // (We could be smarter - sometimes a subset of a sparse dataset will 448 // be dense and VERY occasionally a subset of a relatively dense dataset 449 // will be sparse.) 450 SegmentDataset newData; 451 if (data instanceof SparseSegmentDataset) { 452 newData = 453 new SparseSegmentDataset( 454 newSegment); 455 } else { 456 Object[] newValues = new Object[valueCount]; 457 newData = 458 new DenseSegmentDataset( 459 newSegment, 460 newValues); 461 } 462 463 // If the source is sparse, it is more efficient to iterate over the 464 // values we need. If it's dense, it doesn't matter too much. 465 int[] pos = new int[axes.length]; 466 CellKey newKey = CellKey.Generator.newRefCellKey(pos); 467 data: 468 for (Map.Entry<CellKey, Object> entry : data) { 469 CellKey key = entry.getKey(); 470 471 // Map each of the source coordinates to the target coordinate. 472 // If any of the coordinates maps to null, it means that the 473 // cell falls outside the subset. 474 for (int i = 0; i < pos.length; i++) { 475 int ordinal = key.getAxis(i); 476 477 Map<Integer, Integer> axisPosMap = axisPosMaps[i]; 478 if (axisPosMap == null) { 479 pos[i] = ordinal; 480 } else { 481 Integer integer = axisPosMap.get(ordinal); 482 if (integer == null) { 483 continue data; 484 } 485 pos[i] = integer; 486 } 487 } 488 newData.put(newKey, entry.getValue()); 489 } 490 newSegment.setData(newData, null); 491 492 return newSegment; 493 } 494 495 /** 496 * Returns this Segment's dataset, or null if the data has not yet been 497 * loaded. 498 */ 499 SegmentDataset getData() { 500 return data; 501 } 502 503 /** 504 * <code>State</code> enumerates the allowable values of a segment's 505 * state. 506 */ 507 private static enum State { 508 Initial, Loading, Ready, Failed 509 } 510 511 /** 512 * Definition of a region of values which are not in a segment. 513 * 514 * <p>A region is defined by a set of constraints, one for each column 515 * in the segment. A constraint may be 516 * {@link mondrian.rolap.agg.LiteralStarPredicate}(true), meaning that 517 * the column is unconstrained. 518 * 519 * <p>For example, 520 * <pre> 521 * segment (State={CA, OR, WA}, Gender=*) 522 * actual values {1997, 1998} * {CA, OR, WA} * {M, F} = 12 cells 523 * excluded region (Year=*, State={CA, OR}, Gender={F}) 524 * excluded values {1997, 1998} * {CA, OR} * {F} = 4 cells 525 * 526 * Values: 527 * 528 * F M 529 * CA out in 530 * OR out in 531 * WA in in 532 * </pre> 533 * 534 * <p>Note that the resulting segment is not a hypercube: it has a 'hole'. 535 * This is why regions are required. 536 */ 537 static class Region { 538 private final StarColumnPredicate[] predicates; 539 private final StarPredicate[] multiColumnPredicates; 540 private final int cellCount; 541 542 Region( 543 List<StarColumnPredicate> predicateList, 544 List<StarPredicate> multiColumnPredicateList, 545 int cellCount) 546 { 547 this.predicates = 548 predicateList.toArray( 549 new StarColumnPredicate[predicateList.size()]); 550 this.multiColumnPredicates = 551 multiColumnPredicateList.toArray( 552 new StarPredicate[multiColumnPredicateList.size()]); 553 this.cellCount = cellCount; 554 } 555 556 public List<StarColumnPredicate> getPredicates() { 557 return Collections.unmodifiableList(Arrays.asList(predicates)); 558 } 559 560 public List<StarPredicate> getMultiColumnPredicates() { 561 return Collections.unmodifiableList( 562 Arrays.asList(multiColumnPredicates)); 563 } 564 565 public int getCellCount() { 566 return cellCount; 567 } 568 569 public boolean wouldContain(Object[] keys) { 570 assert keys.length == predicates.length; 571 for (int i = 0; i < keys.length; i++) { 572 final Object key = keys[i]; 573 final StarColumnPredicate predicate = predicates[i]; 574 if (!predicate.evaluate(key)) { 575 return false; 576 } 577 } 578 return true; 579 } 580 581 public boolean equals(Object obj) { 582 if (obj instanceof Region) { 583 Region that = (Region) obj; 584 return Arrays.equals( 585 this.predicates, that.predicates) && 586 Arrays.equals( 587 this.multiColumnPredicates, 588 that.multiColumnPredicates); 589 } else { 590 return false; 591 } 592 } 593 594 public int hashCode() { 595 return Arrays.hashCode(multiColumnPredicates) ^ 596 Arrays.hashCode(predicates); 597 } 598 599 /** 600 * Describes this Segment. 601 * @param buf Buffer to write to. 602 */ 603 public void describe(StringBuilder buf) { 604 int k = 0; 605 for (StarColumnPredicate predicate : predicates) { 606 if (predicate instanceof LiteralStarPredicate && 607 ((LiteralStarPredicate) predicate).getValue()) { 608 continue; 609 } 610 if (k++ > 0) { 611 buf.append(" AND "); 612 } 613 predicate.describe(buf); 614 } 615 for (StarPredicate predicate : multiColumnPredicates) { 616 if (predicate instanceof LiteralStarPredicate && 617 ((LiteralStarPredicate) predicate).getValue()) { 618 continue; 619 } 620 if (k++ > 0) { 621 buf.append(" AND "); 622 } 623 predicate.describe(buf); 624 } 625 } 626 } 627 } 628 629 // End Segment.java