001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/agg/Segment.java#2 $
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-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, 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-release/3.0/src/main/mondrian/rolap/agg/Segment.java#2 $
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