001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/agg/Aggregation.java#3 $
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-2007 Julian Hyde and others
007 // Copyright (C) 2001-2002 Kana Software, Inc.
008 // All Rights Reserved.
009 // You must accept the terms of that agreement to use this software.
010 //
011 // jhyde, 28 August, 2001
012 */
013
014 package mondrian.rolap.agg;
015
016 import mondrian.olap.*;
017 import mondrian.rolap.*;
018
019 import java.io.PrintWriter;
020 import java.lang.ref.SoftReference;
021 import java.util.*;
022 import java.util.concurrent.CopyOnWriteArrayList;
023
024 /**
025 * A <code>Aggregation</code> is a pre-computed aggregation over a set of
026 * columns.
027 *
028 * <p>Rollup operations:<ul>
029 * <li>drop an unrestricted column (e.g. state=*)</li>
030 * <li>tighten any restriction (e.g. year={1997,1998} becomes
031 * year={1997})</li>
032 * <li>restrict an unrestricted column (e.g. year=* becomes
033 * year={1997})</li>
034 * </ul>
035 *
036 * <p>Representation of aggregations. Sparse and dense representations are
037 * necessary for different data sets. Should adapt automatically. Use an
038 * interface to hold the data set, so the segment doesn't care.</p>
039 *
040 * Suppose we have a segment {year=1997, quarter={1,2,3},
041 * state={CA,WA}}. We want to roll up to a segment for {year=1997,
042 * state={CA,WA}}. We need to know that we have all quarters. We don't.
043 * Because year and quarter are independent, we know that we have all of
044 * the ...</p>
045 *
046 * <p>Suppose we have a segment specified by {region=West, state=*,
047 * year=*}, which materializes to ({West}, {CA,WA,OR}, {1997,1998}).
048 * Because state=*, we can rollup to {region=West, year=*} or {region=West,
049 * year=1997}.</p>
050 *
051 * <p>The space required for a segment depends upon the dimensionality (d),
052 * cell count (c) and the value count (v). We don't count the space
053 * required for the actual values, which is the same in any scheme.</p>
054 *
055 * @author jhyde
056 * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/agg/Aggregation.java#3 $
057 * @since 28 August, 2001
058 */
059 public class Aggregation {
060
061 /**
062 * The key that uniquely identify this Aggregation. It is also used
063 * to lookup Aggregation.
064 */
065 private AggregationKey aggregationKey;
066
067 /**
068 * Setting for optimizing sql predicates.
069 */
070 private int maxConstraints;
071
072 /**
073 * List of soft references to segments.
074 * This List implementation should be Thread safe on all mutative operations
075 * (add, set, and so on). Access to this list is not synchronized in the code.
076 * This is the only mutable field in the class.
077 */
078 private final List<SoftReference<Segment>> segmentRefs;
079
080 /**
081 * Timestamp of when the aggregation was created. (We use
082 * {@link java.util.Date} rather than {@link java.sql.Timestamp} because it
083 * has less baggage.)
084 */
085 private final Date creationTimestamp;
086
087 /**
088 * This is set in the load method and is used during
089 * the processing of a particular aggregate load.
090 */
091 private RolapStar.Column[] columns;
092
093 /**
094 * Creates an Aggregation.
095 *
096 * @param aggregationKey the key specifying the axes, the context and
097 * the RolapStar for this Aggregation
098 */
099 public Aggregation(
100 AggregationKey aggregationKey) {
101 this.segmentRefs = getThreadSafeListImplementation();
102 this.maxConstraints =
103 MondrianProperties.instance().MaxConstraints.get();
104 this.creationTimestamp = new Date();
105 this.aggregationKey = aggregationKey;
106
107 }
108
109 private CopyOnWriteArrayList<SoftReference<Segment>> getThreadSafeListImplementation() {
110 return new CopyOnWriteArrayList<SoftReference<Segment>>();
111 }
112
113 /**
114 * @return Returns the timestamp when the aggregation was created
115 */
116 public Date getCreationTimestamp() {
117 return creationTimestamp;
118 }
119
120 /**
121 * Loads a set of segments into this aggregation, one per measure,
122 * each constrained by the same set of column values, and each pinned
123 * once.
124 *
125 * <p>A Column and its constraints are accessed at the same level in their
126 * respective arrays.
127 *
128 * <p>For example,
129 * <blockquote><pre>
130 * measures = {unit_sales, store_sales},
131 * state = {CA, OR},
132 * gender = unconstrained</pre></blockquote>
133 */
134 public void load(
135 RolapStar.Column[] columns,
136 RolapStar.Measure[] measures,
137 StarColumnPredicate[] predicates,
138 RolapAggregationManager.PinSet pinnedSegments,
139 GroupingSetsCollector groupingSetsCollector)
140 {
141 // all constrained columns
142 if (this.columns == null) {
143 this.columns = columns;
144 }
145
146 BitKey measureBitKey = getConstrainedColumnsBitKey().emptyCopy();
147 int axisCount = columns.length;
148 Util.assertTrue(predicates.length == axisCount);
149
150 // This array of Aggregation.Axis is shared by all Segments for
151 // this set of measures and constraints
152 Aggregation.Axis[] axes = new Aggregation.Axis[axisCount];
153 for (int i = 0; i < axisCount; i++) {
154 axes[i] = new Aggregation.Axis(predicates[i]);
155 }
156 Segment[] segments =
157 addSegmentsToAggregation(
158 measures, measureBitKey, axes, pinnedSegments);
159 // The constrained columns are simply the level and foreign columns
160 BitKey levelBitKey = getConstrainedColumnsBitKey();
161 GroupingSet groupingSet =
162 new GroupingSet(
163 segments, levelBitKey, measureBitKey, axes, columns);
164 final List<StarPredicate> compoundPredicateList =
165 aggregationKey.getCompoundPredicateList();
166 if (groupingSetsCollector.useGroupingSets()) {
167 groupingSetsCollector.add(groupingSet);
168 // Segments are loaded using group by grouping sets
169 // by CompositeBatch.loadAggregation
170 } else {
171 new SegmentLoader().load(
172 Collections.singletonList(groupingSet),
173 pinnedSegments,
174 compoundPredicateList);
175 }
176 }
177
178 private Segment[] addSegmentsToAggregation(
179 RolapStar.Measure[] measures,
180 BitKey measureBitKey,
181 Axis[] axes,
182 RolapAggregationManager.PinSet pinnedSegments)
183 {
184 Segment[] segments = new Segment[measures.length];
185 for (int i = 0; i < measures.length; i++) {
186 RolapStar.Measure measure = measures[i];
187 measureBitKey.set(measure.getBitPosition());
188 Segment segment =
189 new Segment(
190 this, measure, axes,
191 Collections.<Segment.Region>emptyList());
192 segments[i] = segment;
193 SoftReference<Segment> ref = new SoftReference<Segment>(segment);
194 segmentRefs.add(ref);
195 ((AggregationManager.PinSetImpl) pinnedSegments).add(segment);
196 }
197 return segments;
198 }
199
200 /**
201 * Drops predicates, where the list of values is close to the values which
202 * would be returned anyway.
203 */
204 public StarColumnPredicate[] optimizePredicates(
205 RolapStar.Column[] columns,
206 StarColumnPredicate[] predicates) {
207 RolapStar star = getStar();
208 Util.assertTrue(predicates.length == columns.length);
209 StarColumnPredicate[] newPredicates = predicates.clone();
210 double[] bloats = new double[columns.length];
211
212 // We want to handle the special case "drilldown" which occurs pretty
213 // often. Here, the parent is here as a constraint with a single member
214 // and the list of children as well.
215 List<Member> potentialParents = new ArrayList<Member>();
216 for (final StarColumnPredicate predicate : predicates) {
217 Member m;
218 if (predicate instanceof MemberColumnPredicate) {
219 m = ((MemberColumnPredicate) predicate).getMember();
220 potentialParents.add(m);
221 }
222 }
223
224 for (int i = 0; i < newPredicates.length; i++) {
225 // A set of constraints with only one entry will not be optimized away
226 if (!(newPredicates[i] instanceof ListColumnPredicate)) {
227 bloats[i] = 0.0;
228 continue;
229 }
230
231 final ListColumnPredicate newPredicate =
232 (ListColumnPredicate) newPredicates[i];
233 final List<StarColumnPredicate> predicateList =
234 newPredicate.getPredicates();
235 final int valueCount = predicateList.size();
236 if (valueCount < 2) {
237 bloats[i] = 0.0;
238 continue;
239 }
240
241 if (valueCount > maxConstraints) {
242 // Some databases can handle only a limited number of elements
243 // in 'WHERE IN (...)'. This set is greater than this database
244 // can handle, so we drop this constraint. Hopefully there are
245 // other constraints that will limit the result.
246 bloats[i] = 1.0; // will be optimized away
247 continue;
248 }
249
250 // more than one - check for children of same parent
251 double constraintLength = (double) valueCount;
252 Member parent = null;
253 Level level = null;
254 for (int j = 0; j < valueCount; j++) {
255 Object value = predicateList.get(j);
256 if (value instanceof MemberColumnPredicate) {
257 MemberColumnPredicate memberColumnPredicate =
258 (MemberColumnPredicate) value;
259 Member m = memberColumnPredicate.getMember();
260 if (j == 0) {
261 parent = m.getParentMember();
262 level = m.getLevel();
263 } else {
264 if (parent != null
265 && !parent.equals(m.getParentMember())) {
266 parent = null; // no common parent
267 }
268 if (level != null
269 && !level.equals(m.getLevel())) {
270 // should never occur, constraints are of same level
271 level = null;
272 }
273 }
274 } else {
275 // Value constraint with no associated member.
276 // Compute bloat by #constraints / column cardinality.
277 parent = null;
278 level = null;
279 bloats[i] = constraintLength / columns[i].getCardinality();
280 break;
281 }
282 }
283 boolean done = false;
284 if (parent != null) {
285 // common parent exists
286 if (parent.isAll() || potentialParents.contains(parent)) {
287 // common parent is there as constraint
288 // if the children are complete, this constraint set is
289 // unneccessary try to get the children directly from
290 // cache for the drilldown case, the children will be
291 // in the cache
292 // - if not, forget this optimization.
293 SchemaReader scr = star.getSchema().getSchemaReader();
294 int childCount = scr.getChildrenCountFromCache(parent);
295 if (childCount == -1) {
296 // nothing gotten from cache
297 if (!parent.isAll()) {
298 // parent is in constraints
299 // no information about children cardinality
300 // constraints must not be optimized away
301 bloats[i] = 0.0;
302 done = true;
303 }
304 } else {
305 bloats[i] = constraintLength / childCount;
306 done = true;
307 }
308 }
309 }
310
311 if (!done && level != null) {
312 // if the level members are cached, we do not need "count *"
313 SchemaReader scr = star.getSchema().getSchemaReader();
314 int memberCount = scr.getLevelCardinality(level, true, false);
315 if (memberCount > 0) {
316 bloats[i] = constraintLength / memberCount;
317 done = true;
318 }
319 }
320
321 if (!done) {
322 bloats[i] = constraintLength / columns[i].getCardinality();
323 }
324 }
325
326 // build a list of constraints sorted by 'bloat factor'
327 ConstraintComparator comparator = new ConstraintComparator(bloats);
328 Integer[] indexes = new Integer[columns.length];
329 for (int i = 0; i < columns.length; i++) {
330 indexes[i] = i;
331 }
332
333 // sort indexes by bloat descending
334 Arrays.sort(indexes, comparator);
335
336 // Eliminate constraints one by one, until the constrained cell count
337 // became half of the unconstrained cell count. We can not have an
338 // absolute value here, because its
339 // very different if we fetch data for 2 years or 10 years (5 times
340 // more means 5 times slower). So a relative comparison is ok here
341 // but not an absolute one.
342
343 double abloat = 1.0;
344 final double aBloatLimit = .5;
345
346 for (Integer j : indexes) {
347 abloat = abloat * bloats[j];
348 if (abloat <= aBloatLimit) {
349 break;
350 }
351 // eliminate this constraint
352 if (MondrianProperties.instance().OptimizePredicates.get()
353 || bloats[j] == 1) {
354 newPredicates[j] = new LiteralStarPredicate(columns[j], true);
355 }
356 }
357 return newPredicates;
358 }
359
360 public String toString() {
361 java.io.StringWriter sw = new java.io.StringWriter(256);
362 PrintWriter pw = new PrintWriter(sw);
363 print(pw);
364 pw.flush();
365 return sw.toString();
366 }
367
368 /**
369 * Prints the state of this <code>Aggregation</code> to a writer.
370 *
371 * @param pw Writer
372 */
373 public void print(PrintWriter pw) {
374 List<Segment> segmentList = new ArrayList<Segment>();
375 for (SoftReference<Segment> ref : segmentRefs) {
376 Segment segment = ref.get();
377 if (segment == null) {
378 continue;
379 }
380 segmentList.add(segment);
381 }
382
383 // Sort segments, to make order deterministic.
384 Collections.sort(
385 segmentList,
386 new Comparator<Segment>() {
387 public int compare(Segment o1, Segment o2) {
388 return o1.id - o2.id;
389 }
390 });
391
392 for (Segment segment : segmentList) {
393 segment.print(pw);
394 }
395 }
396
397 public void flush(
398 CacheControl cacheControl,
399 RolapCacheRegion cacheRegion) {
400 // Compare the bitmaps.
401 //
402 // Case 1: aggregate bitmap contains request bitmap.
403 // E.g. agg = (year={1997, 1998}, quarter=*, nation=USA),
404 // request = (year=1997, nation={USA, Canada}).
405 // Assuming descendants (which we do, for now) flush the segment
406 // based on the {Year, Nation} values:
407 // flush = (year=1997, quarter=*, nation=USA)
408 //
409 // Case 2: aggregate bitmap is strict subset of request bitmap
410 // E.g. agg = (year={1997, 1998}, nation=*)
411 // request = (year={1997}, nation=*, gender="F")
412 // This segment isn't constrained on gender, therefore all cells could
413 // contain gender="F" values:
414 // flush = (year=1997, nation=*)
415 //
416 // Case 3: no overlap
417 // E.g. agg = (product, gender),
418 // request = (year=1997)
419 // This segment isn't constrained on year, therefore all cells could
420 // contain 1997 values. Flush the whole segment.
421 //
422 // The rule is:
423 // - Column in flush request and in segment. Apply constraints.
424 // - Column in flush request, not in segment. Ignore it.
425 // - Column not in flush request, in segment. Ignore it.
426 final boolean bitmapsIntersect =
427 cacheRegion.getConstrainedColumnsBitKey().intersects(
428 getConstrainedColumnsBitKey());
429
430 // New list of segments - will replace segmentRefs when we're done.
431 List<SoftReference<Segment>> newSegmentRefs =
432 new ArrayList<SoftReference<Segment>>();
433 segmentLoop:
434 for (SoftReference<Segment> segmentRef : segmentRefs) {
435 Segment segment = segmentRef.get();
436 if (segment == null) {
437 // Segment has been garbage collected. Flush it.
438 cacheControl.trace("discarding garbage collected segment");
439 continue;
440 }
441 if (!bitmapsIntersect) {
442 // No intersection between the columns constraining the flush
443 // and the columns defining the segment. Therefore, the segment
444 // is definitely affected. Flush it.
445 cacheControl.trace(
446 "discard segment - it has no columns in common: " +
447 segment);
448 continue;
449 }
450
451 // For each axis, indicates which values will be retained when
452 // constraints have been applied.
453 BitSet[] axisKeepBitSets = new BitSet[columns.length];
454 for (int i = 0; i < columns.length; i++) {
455 final Axis axis = segment.axes[i];
456 int keyCount = axis.getKeys().length;
457 final BitSet axisKeepBitSet
458 = axisKeepBitSets[i]
459 = new BitSet(keyCount);
460 final StarColumnPredicate predicate = axis.predicate;
461 assert predicate != null;
462
463 RolapStar.Column column = columns[i];
464 if (!cacheRegion.getConstrainedColumnsBitKey().get(
465 column.getBitPosition())) {
466 axisKeepBitSet.set(0, keyCount);
467 continue;
468 }
469 StarColumnPredicate flushPredicate =
470 cacheRegion.getPredicate(column.getBitPosition());
471
472 // If the flush request is not constrained on this column, move
473 // on to the next column.
474 if (flushPredicate == null) {
475 axisKeepBitSet.set(0, keyCount);
476 continue;
477 }
478
479 // If the segment is constrained on this column,
480 // and the flush request is constrained on this column,
481 // and the constraints do not intersect,
482 // then this flush request does not affect this segment.
483 // Keep it.
484 if (!flushPredicate.mightIntersect(predicate)) {
485 newSegmentRefs.add(segmentRef);
486 continue segmentLoop;
487 }
488
489 // The flush constraints overlap. We need to create a new
490 // constraint which captures what is actually in this segment.
491 //
492 // After the flush, values explicitly flushed must be outside
493 // the constraints of the axis. In particular, if the axis is
494 // initially unconstrained, contains the values {X, Y, Z}, and
495 // value Z is flushed, the new constraint of the axis will be
496 // {X, Y}. This will force the reader to look to another segment
497 // for the Z value, rather than assuming that it does not exist.
498 //
499 // Example #1. Column constraint is {A, B, C},
500 // actual values are {A, B},
501 // flush is {A, D}. New constraint could be
502 // either {B, C} (constraint minus flush)
503 // or {B} (actual minus flush).
504 //
505 // Example #2. Column constraint is * (unconstrained),
506 // actual values are {A, B},
507 // flush is {A, D}. New constraint must be
508 // {B} (actual minus flush) because mondrian cannot model
509 // negative constraints on segments.
510 final Object[] axisKeys = axis.getKeys();
511 for (int k = 0; k < axisKeys.length; k++) {
512 Object key = axisKeys[k];
513 if (!flushPredicate.evaluate(key)) {
514 axisKeepBitSet.set(k);
515 }
516 }
517 }
518
519 // Now go through the multi-column constraints, and eliminate any
520 // values which are always blocked by a given predicate.
521 for (StarPredicate predicate : cacheRegion.getPredicates()) {
522 ValuePruner pruner =
523 new ValuePruner(
524 predicate,
525 segment.axes,
526 segment.getData());
527 pruner.go(axisKeepBitSets);
528 }
529
530 // Figure out which of the axes retains most of its values.
531 float bestRetention = 0f;
532 int bestColumn = -1;
533 for (int i = 0; i < columns.length; i++) {
534 // What proportion of the values on this axis survived the flush
535 // constraint? 1.0 means they all survived. This means that none
536 // of the cells in the segment will be discarded.
537 // But we still need to tighten the constraints on the
538 // segment, in case new axis values have appeared.
539 RolapStar.Column column = columns[i];
540 final int bitPosition = column.getBitPosition();
541 if (!cacheRegion.getConstrainedColumnsBitKey().get(bitPosition)) {
542 continue;
543 }
544
545 final BitSet axisBitSet = axisKeepBitSets[i];
546 final Axis axis = segment.axes[i];
547 final Object[] axisKeys = axis.getKeys();
548
549 if (axisBitSet.cardinality() == 0) {
550 // If one axis is empty, the entire segment is empty.
551 // Discard it.
552 continue segmentLoop;
553 }
554
555 float retention =
556 (float) axisBitSet.cardinality() /
557 (float) axisKeys.length;
558
559 if (bestColumn == -1 || retention > bestRetention) {
560 // If there are multiple partially-satisfied
561 // constraints ANDed together, keep the constraint
562 // which is least selective.
563 bestRetention = retention;
564 bestColumn = i;
565 }
566 }
567
568 // Come up with an estimate of how many cells this region contains.
569 List<StarColumnPredicate> regionPredicates =
570 new ArrayList<StarColumnPredicate>();
571 int cellCount = 1;
572 for (int i = 0; i < this.columns.length; i++) {
573 RolapStar.Column column = this.columns[i];
574 Axis axis = segment.axes[i];
575 final int pos = column.getBitPosition();
576 StarColumnPredicate flushPredicate =
577 cacheRegion.getPredicate(pos);
578 int keysMatched;
579 if (flushPredicate == null) {
580 flushPredicate = LiteralStarPredicate.TRUE;
581 keysMatched = axis.getKeys().length;
582 } else {
583 keysMatched = axis.getMatchCount(flushPredicate);
584 }
585 cellCount *= keysMatched;
586 regionPredicates.add(flushPredicate);
587 }
588
589 // We don't know the selectivity of multi-column predicates
590 // (typically member predicates such as '>= [Time].[1997].[Q2]') so
591 // we guess 50% selectivity.
592 for (StarPredicate p : cacheRegion.getPredicates()) {
593 cellCount *= .5;
594 }
595 Segment.Region region =
596 new Segment.Region(
597 regionPredicates,
598 new ArrayList<StarPredicate>(cacheRegion.getPredicates()),
599 cellCount);
600
601 // How many cells left after we exclude this region? If there are
602 // none left, throw away the segment. It doesn't matter if we
603 // over-estimate how many cells are in the region, and therefore
604 // throw away a segment which has a few cells left.
605 int remainingCellCount = segment.getCellCount();
606 if (remainingCellCount - cellCount <= 0) {
607 continue;
608 }
609
610 // Add the flush region to the list of excluded regions.
611 //
612 // TODO: If the region has been fully accounted for in changes to
613 // the predicates on the axes, then don't add it to the exclusion
614 // list.
615 final List<Segment.Region> excludedRegions =
616 new ArrayList<Segment.Region>(segment.getExcludedRegions());
617 if (!excludedRegions.contains(region)) {
618 excludedRegions.add(region);
619 }
620
621 StarColumnPredicate bestColumnPredicate;
622 if (bestColumn >= 0) {
623 // Instantiate the axis with the best retention.
624 RolapStar.Column column = columns[bestColumn];
625 final int bitPosition = column.getBitPosition();
626 StarColumnPredicate flushPredicate =
627 cacheRegion.getPredicate(bitPosition);
628 final Axis axis = segment.axes[bestColumn];
629 bestColumnPredicate = axis.predicate;
630 if (flushPredicate != null) {
631 bestColumnPredicate =
632 bestColumnPredicate.minus(flushPredicate);
633 }
634 } else {
635 bestColumnPredicate = null;
636 }
637
638 final Segment newSegment =
639 segment.createSubSegment(
640 axisKeepBitSets,
641 bestColumn,
642 bestColumnPredicate,
643 excludedRegions);
644
645 newSegmentRefs.add(new SoftReference<Segment>(newSegment));
646 }
647
648 // Replace list of segments.
649 // FIXME: Synchronize.
650 // TODO: Replace segmentRefs, don't copy.
651 segmentRefs.clear();
652 segmentRefs.addAll(newSegmentRefs);
653 }
654
655 /**
656 * Retrieves the value identified by <code>keys</code>.
657 * If the requested cell is found in the loading segment, current Thread
658 * will be blocked until segment is loaded.
659 *
660 * <p>If <code>pinSet</code> is not null, pins the
661 * segment which holds it. <code>pinSet</code> ensures that a segment is
662 * only pinned once.
663 *
664 * <p>Returns <code>null</code> if no segment contains the cell.
665 *
666 * <p>Returns {@link Util#nullValue} if a segment contains the cell and the
667 * cell's value is null.
668 */
669 public Object getCellValue(
670 RolapStar.Measure measure,
671 Object[] keys,
672 RolapAggregationManager.PinSet pinSet) {
673 for (SoftReference<Segment> segmentref : segmentRefs) {
674 Segment segment = segmentref.get();
675 if (segment == null) {
676 segmentRefs.remove(segmentref);
677 continue; // it's being garbage-collected
678 }
679 if (segment.measure != measure) {
680 continue;
681 }
682 if (segment.isReady()) {
683 Object o = segment.getCellValue(keys);
684 if (o != null) {
685 if (pinSet != null) {
686 ((AggregationManager.PinSetImpl) pinSet).add(segment);
687 }
688 return o;
689 }
690 } else {
691 // avoid to call wouldContain - its slow
692 if (pinSet != null
693 && !((AggregationManager.PinSetImpl) pinSet).contains(segment)
694 && segment.wouldContain(keys)) {
695 segment.waitUntilLoaded(); //Waiting on Segment state
696 if (segment.isReady()) {
697 ((AggregationManager.PinSetImpl) pinSet).add(segment);
698 return segment.getCellValue(keys);
699 }
700 }
701 }
702 }
703 // No segment contains the requested cell.
704 return null;
705 }
706
707 /**
708 * This is called during Sql generation.
709 */
710 public RolapStar.Column[] getColumns() {
711 return columns;
712 }
713
714 /**
715 * This is called during Sql generation.
716 */
717 public RolapStar getStar() {
718 return aggregationKey.getStar();
719 }
720
721 /**
722 * Returns the BitKey for ALL columns (Measures and Levels) involved in the
723 * query.
724 */
725 public BitKey getConstrainedColumnsBitKey() {
726 return aggregationKey.getConstrainedColumnsBitKey();
727 }
728
729 // -- classes -------------------------------------------------------------
730
731 static class Axis {
732
733 /**
734 * Constraint on the keys in this Axis. Never null.
735 */
736 private final StarColumnPredicate predicate;
737
738 /**
739 * Map holding the position of each key value.
740 *
741 * <p>TODO: Hold keys in a sorted array, then deduce ordinal by doing
742 * binary search.
743 */
744 private final Map<Comparable<?>, Integer> mapKeyToOffset =
745 new HashMap<Comparable<?>, Integer>();
746
747 /**
748 * Actual key values retrieved.
749 */
750 private Comparable<?>[] keys;
751 private boolean hasNull;
752
753 /**
754 * Creates an empty Axis.
755 *
756 * @param predicate Predicate defining which keys should appear on
757 * axis. (If a key passes the predicate but is not in the list, every
758 * cell with that key is assumed to have a null value.)
759 */
760 Axis(StarColumnPredicate predicate) {
761 this.predicate = predicate;
762 assert predicate != null;
763 }
764
765 /**
766 * Creates an axis populated with a set of keys.
767 *
768 * @param predicate Predicate defining which keys should appear on
769 * axis. (If a key passes the predicate but is not in the list, every
770 * cell with that key is assumed to have a null value.)
771 * @param keys Keys
772 */
773 Axis(StarColumnPredicate predicate, Comparable<?>[] keys) {
774 this(predicate);
775 this.keys = keys;
776 for (int i = 0; i < keys.length; i++) {
777 Comparable<?> key = keys[i];
778 mapKeyToOffset.put(key, i);
779 assert i == 0 ||
780 ((Comparable) keys[i - 1]).compareTo(keys[i]) < 0;
781 }
782 }
783
784 StarColumnPredicate getPredicate() {
785 return predicate;
786 }
787
788 Comparable<?>[] getKeys() {
789 return this.keys;
790 }
791
792 /**
793 * Loads keys into the axis.
794 *
795 * @param valueSet Set of distinct key values, sorted
796 * @param hasNull Whether the axis contains the null value, in addition
797 * to the values in <code>valueSet</code>
798 * @return Number of keys on axis
799 */
800 int loadKeys(SortedSet<Comparable<?>> valueSet, boolean hasNull) {
801 this.hasNull = hasNull;
802 int size = valueSet.size();
803
804 if (hasNull) {
805 size++;
806 }
807 keys = new Comparable<?>[size];
808
809 valueSet.toArray(keys);
810 if (hasNull) {
811 keys[size - 1] = RolapUtil.sqlNullValue;
812 }
813
814 for (int i = 0; i < size; i++) {
815 mapKeyToOffset.put(keys[i], i);
816 }
817
818 return size;
819 }
820
821 static final Comparable wrap(Object o) {
822 if (Util.PreJdk15 && o instanceof Boolean) {
823 return Integer.valueOf((Boolean) o ? 1 : 0);
824 } else {
825 return (Comparable) o;
826 }
827 }
828
829 final int getOffset(Object o) {
830 return getOffset(wrap(o));
831 }
832
833 final int getOffset(Comparable key) {
834 Integer ordinal = mapKeyToOffset.get(key);
835 if (ordinal == null) {
836 return -1;
837 }
838 return ordinal.intValue();
839 }
840
841 /**
842 * Returns whether this axis contains a given key, or would contain it
843 * if it existed.
844 *
845 * <p>For example, if this axis is unconstrained, then this method
846 * returns <code>true</code> for any value.
847 *
848 * @param key Key
849 * @return Whether this axis would contain <code>key</code>
850 */
851 boolean contains(Object key) {
852 return predicate.evaluate(key);
853 }
854
855 /**
856 * Returns how many of this Axis' keys match a given constraint.
857 *
858 * @param predicate Predicate
859 * @return How many keys match constraint
860 */
861 public int getMatchCount(StarColumnPredicate predicate) {
862 int matchCount = 0;
863 for (Object key : keys) {
864 if (predicate.evaluate(key)) {
865 ++matchCount;
866 }
867 }
868 return matchCount;
869 }
870 }
871
872 /**
873 * Helper class to figure out which axis values evaluate to true at least
874 * once by a given predicate.
875 *
876 * <p>Consider, for example, the flush predicate<blockquote><code>
877 *
878 * member between [Time].[1997].[Q3] and [Time].[1999].[Q1]
879 *
880 * </code></blockquote>applied to the segment <blockquote><code>
881 *
882 * year in (1996, 1997, 1998, 1999)<br/>
883 * quarter in (Q1, Q2, Q3, Q4)
884 *
885 * </code></blockquote> The predicate evaluates to true for the pairs
886 * <blockquote><code>
887 *
888 * {(1997, Q3), (1997, Q4),
889 * (1998, Q1), (1998, Q2), (1998, Q3), (1998, Q4), (1999, Q1)}
890 *
891 * </code></blockquote> and therefore we wish to eliminate these pairs from
892 * the segment. But we can eliminate a value only if <em>all</em> of its
893 * values are eliminated.
894 *
895 * <p>In this case, year=1998 is the only value which can be eliminated from
896 * the segment.
897 */
898 private static class ValuePruner {
899 /**
900 * Multi-column predicate. If the predicate evaluates to true, a cell
901 * will be removed from the segment. But we can only eliminate a value
902 * if all of its cells are eliminated.
903 */
904 private final StarPredicate flushPredicate;
905 /**
906 * Number of columns predicate depends on.
907 */
908 private final int arity;
909 /**
910 * For each column, the segment axis which the column corresponds to, or
911 * null.
912 */
913 private final Axis[] axes;
914 /**
915 * For each column, a bitmap of values for which the predicate is
916 * sometimes false. These values cannot be eliminated from the axis.
917 */
918 private final BitSet[] keepBitSets;
919 /**
920 * For each segment axis, the predicate column which depends on the
921 * axis, or -1.
922 */
923 private final int[] axisInverseOrdinals;
924 /**
925 * Workspace which contains the current key value for each column.
926 */
927 private final Object[] values;
928 /**
929 * View onto {@link #values} as a list.
930 */
931 private final List<Object> valueList;
932 /**
933 * Workspace which contains the ordinal of the current value of each
934 * column on its axis.
935 */
936 private final int[] ordinals;
937
938 private final SegmentDataset data;
939
940 private final CellKey cellKey;
941
942 /**
943 * Creates a ValuePruner.
944 *
945 * @param flushPredicate Multi-column predicate to test
946 * @param segmentAxes Axes of the segment. (The columns that the
947 * predicate may not be present, or may be in a different order.)
948 * @param data Segment dataset, which allows pruner to determine whether
949 * a particular cell is currently empty
950 */
951 ValuePruner(
952 StarPredicate flushPredicate,
953 Axis[] segmentAxes,
954 SegmentDataset data) {
955 this.flushPredicate = flushPredicate;
956 this.arity = flushPredicate.getConstrainedColumnList().size();
957 this.axes = new Axis[arity];
958 this.keepBitSets = new BitSet[arity];
959 this.axisInverseOrdinals = new int[segmentAxes.length];
960 Arrays.fill(axisInverseOrdinals, -1);
961 this.values = new Object[arity];
962 this.valueList = Arrays.asList(values);
963 this.ordinals = new int[arity];
964 assert data != null;
965 this.data = data;
966 this.cellKey = CellKey.Generator.newCellKey(segmentAxes.length);
967
968 // Pair up constraint columns with axes. If one of the constraint's
969 // columns is not in this segment, it gets the null axis. The
970 // constraint will have to evaluate to true for all possible values
971 // of that column.
972 for (int i = 0; i < arity; i++) {
973 RolapStar.Column column =
974 flushPredicate.getConstrainedColumnList().get(i);
975 int axisOrdinal =
976 findAxis(segmentAxes, column.getBitPosition());
977 if (axisOrdinal < 0) {
978 this.axes[i] = null;
979 values[i] = StarPredicate.WILDCARD;
980 keepBitSets[i] = new BitSet(1); // dummy
981 } else {
982 axes[i] = segmentAxes[axisOrdinal];
983 axisInverseOrdinals[axisOrdinal] = i;
984 final int keyCount = axes[i].getKeys().length;
985 keepBitSets[i] = new BitSet(keyCount);
986 }
987 }
988 }
989
990 private int findAxis(Axis[] axes, int bitPosition) {
991 for (int i = 0; i < axes.length; i++) {
992 Axis axis = axes[i];
993 if (axis.getPredicate().getConstrainedColumn().getBitPosition()
994 == bitPosition) {
995 return i;
996 }
997 }
998 return -1;
999 }
1000
1001 /**
1002 * Applies this ValuePruner's predicate and sets bits in axisBitSets
1003 * to indicate extra values which can be removed.
1004 *
1005 * @param axisKeepBitSets Array containing, for each axis, a bitset
1006 * of values to keep (not flush)
1007 */
1008 void go(BitSet[] axisKeepBitSets) {
1009 evaluatePredicate(0);
1010
1011 // Clear bits in the axis bit sets (indicating that a value is never
1012 // used) if this predicate evaluates to true for every combination
1013 // of values which this axis value appears in.
1014 for (int i = 0; i < axisKeepBitSets.length; i++) {
1015 if (axisInverseOrdinals[i] < 0) {
1016 continue;
1017 }
1018 BitSet axisKeepBitSet = axisKeepBitSets[axisInverseOrdinals[i]];
1019 final BitSet keepBitSet = keepBitSets[i];
1020 axisKeepBitSet.and(keepBitSet);
1021 }
1022 }
1023
1024 /**
1025 * Evaluates the predicate for axes <code>i</code> and higher, and marks
1026 * {@link #keepBitSets} if the predicate ever evaluates to false.
1027 * The result is that discardBitSets[i] will be false for column #i if
1028 * the predicate evaluates to true for all cells in the segment which
1029 * have that column value.
1030 *
1031 * @param axisOrdinal Axis ordinal
1032 */
1033 private void evaluatePredicate(int axisOrdinal) {
1034 if (axisOrdinal == arity) {
1035 // If the flush predicate evaluates to false for this cell,
1036 // and this cell currently has some data (*),
1037 // then none of the values which are the coordinates of this
1038 // cell can be discarded.
1039 //
1040 // * Important when there is sparsity. Consider the cell
1041 // {year=1997, quarter=Q1, month=12}. This cell would never have
1042 // data, so there's no point keeping it.
1043 if (!flushPredicate.evaluate(valueList)) {
1044 if (data.get(cellKey) != null) {
1045 for (int k = 0; k < arity; k++) {
1046 keepBitSets[k].set(ordinals[k]);
1047 }
1048 }
1049 }
1050 } else {
1051 final Axis axis = axes[axisOrdinal];
1052 if (axis == null) {
1053 evaluatePredicate(axisOrdinal + 1);
1054 } else {
1055 for (int keyOrdinal = 0; keyOrdinal < axis.keys.length; keyOrdinal++) {
1056 Object key = axis.keys[keyOrdinal];
1057 values[axisOrdinal] = key;
1058 ordinals[axisOrdinal] = keyOrdinal;
1059 cellKey.setAxis(
1060 axisInverseOrdinals[axisOrdinal],
1061 keyOrdinal);
1062 evaluatePredicate(axisOrdinal + 1);
1063 }
1064 }
1065 }
1066 }
1067 }
1068
1069 private static class ConstraintComparator implements Comparator<Integer> {
1070 private final double[] bloats;
1071
1072 ConstraintComparator(double[] bloats) {
1073 this.bloats = bloats;
1074 }
1075
1076 // implement Comparator
1077 // order by bloat descending
1078 public int compare(Integer o0, Integer o1) {
1079 double bloat0 = bloats[o0];
1080 double bloat1 = bloats[o1];
1081 return (bloat0 == bloat1)
1082 ? 0
1083 : (bloat0 < bloat1)
1084 ? 1
1085 : -1;
1086 }
1087 }
1088
1089
1090 }
1091
1092 // End Aggregation.java