001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2002-2005 Julian Hyde 008// Copyright (C) 2005-2012 Pentaho and others 009// All Rights Reserved. 010// 011// jhyde, 21 March, 2002 012*/ 013package mondrian.rolap.agg; 014 015import mondrian.olap.Util; 016import mondrian.rolap.*; 017import mondrian.spi.SegmentHeader; 018 019import org.apache.log4j.Logger; 020 021import java.io.PrintWriter; 022import java.util.*; 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 034 * this 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 041 * Aggregation. Aggregations belong to the AggregationManager, a singleton.</p> 042 * 043 * <p>Segments are pinned during the evaluation of a single MDX query. The query 044 * evaluates the expressions twice. The first pass, it finds which cell values 045 * it needs, pins the segments containing the ones which are already present 046 * (one pin-count for each cell value used), and builds a {@link CellRequest 047 * cell request} for those which are not present. It executes the cell request 048 * to bring the required cell values into the cache, again, pinned. Then it 049 * evalutes the query a second time, knowing that all cell values are 050 * available. Finally, it releases the pins.</p> 051 * 052 * <p>A Segment may have a list of {@link ExcludedRegion} objects. These are 053 * caused by cache flushing. Usually a segment is a hypercube: it is defined by 054 * a set of values on each of its axes. But after a cache flush request, a 055 * segment may have a rectangular 'hole', and therefore not be a hypercube 056 * anymore. 057 * 058 * <p>For example, the segment defined by {CA, OR, WA} * {F, M} is a 059 * 2-dimensional hyper-rectangle with 6 cells. After flushing {CA, OR, TX} * 060 * {F}, the result is 4 cells: 061 * 062 * <pre> 063 * F M 064 * CA out in 065 * OR out in 066 * WA in in 067 * </pre> 068 * 069 * defined by the original segment minus the region ({CA, OR} * {F}). 070 * 071 * @author jhyde 072 * @since 21 March, 2002 073 */ 074public class Segment { 075 private static int nextId = 0; // generator for "id" 076 077 final int id; // for debug 078 private String desc; 079 080 /** 081 * This is set in the load method and is used during 082 * the processing of a particular aggregate load. 083 */ 084 protected final RolapStar.Column[] columns; 085 086 public final RolapStar.Measure measure; 087 088 /** 089 * An array of axes, one for each constraining column, containing the values 090 * returned for that constraining column. 091 */ 092 public final StarColumnPredicate[] predicates; 093 094 protected final RolapStar star; 095 protected final BitKey constrainedColumnsBitKey; 096 097 /** 098 * List of regions to ignore when reading this segment. This list is 099 * populated when a region is flushed. The cells for these regions may be 100 * physically in the segment, because trimming segments can be expensive, 101 * but should still be ignored. 102 */ 103 protected final List<ExcludedRegion> excludedRegions; 104 105 private final int aggregationKeyHashCode; 106 protected final List<StarPredicate> compoundPredicateList; 107 108 private final SegmentHeader segmentHeader; 109 110 private static final Logger LOGGER = Logger.getLogger(Segment.class); 111 112 /** 113 * Creates a <code>Segment</code>; it's not loaded yet. 114 * 115 * @param star Star that this Segment belongs to 116 * @param measure Measure whose values this Segment contains 117 * @param predicates List of predicates constraining each axis 118 * @param excludedRegions List of regions which are not in this segment. 119 */ 120 public Segment( 121 RolapStar star, 122 BitKey constrainedColumnsBitKey, 123 RolapStar.Column[] columns, 124 RolapStar.Measure measure, 125 StarColumnPredicate[] predicates, 126 List<ExcludedRegion> excludedRegions, 127 final List<StarPredicate> compoundPredicateList) 128 { 129 this.id = nextId++; 130 this.star = star; 131 this.constrainedColumnsBitKey = constrainedColumnsBitKey; 132 this.columns = columns; 133 this.measure = measure; 134 this.predicates = predicates; 135 this.excludedRegions = excludedRegions; 136 this.compoundPredicateList = compoundPredicateList; 137 final List<BitKey> compoundPredicateBitKeys = 138 compoundPredicateList == null 139 ? null 140 : new AbstractList<BitKey>() { 141 public BitKey get(int index) { 142 return compoundPredicateList.get(index) 143 .getConstrainedColumnBitKey(); 144 } 145 146 public int size() { 147 return compoundPredicateList.size(); 148 } 149 }; 150 this.aggregationKeyHashCode = 151 AggregationKey.computeHashCode( 152 constrainedColumnsBitKey, 153 star, 154 compoundPredicateBitKeys); 155 this.segmentHeader = SegmentBuilder.toHeader(this); 156 } 157 158 /** 159 * Returns the constrained columns. 160 */ 161 public RolapStar.Column[] getColumns() { 162 return columns; 163 } 164 165 /** 166 * Returns the star. 167 */ 168 public RolapStar getStar() { 169 return star; 170 } 171 172 /** 173 * Returns the list of compound predicates. 174 */ 175 public List<StarPredicate> getCompoundPredicateList() { 176 return compoundPredicateList; 177 } 178 179 /** 180 * Returns the BitKey for ALL columns (Measures and Levels) involved in the 181 * query. 182 */ 183 public BitKey getConstrainedColumnsBitKey() { 184 return constrainedColumnsBitKey; 185 } 186 187 private void describe(StringBuilder buf, boolean values) { 188 final String sep = Util.nl + " "; 189 buf.append(printSegmentHeaderInfo(sep)); 190 191 for (int i = 0; i < columns.length; i++) { 192 buf.append(sep); 193 buf.append(columns[i].getExpression().getGenericExpression()); 194 describeAxes(buf, i, values); 195 } 196 if (!excludedRegions.isEmpty()) { 197 buf.append(sep); 198 buf.append("excluded={"); 199 int k = 0; 200 for (ExcludedRegion excludedRegion : excludedRegions) { 201 if (k++ > 0) { 202 buf.append(", "); 203 } 204 excludedRegion.describe(buf); 205 } 206 buf.append('}'); 207 } 208 buf.append('}'); 209 } 210 211 protected void describeAxes(StringBuilder buf, int i, boolean values) { 212 predicates[i].describe(buf); 213 } 214 215 private String printSegmentHeaderInfo(String sep) { 216 StringBuilder buf = new StringBuilder(); 217 buf.append("Segment #"); 218 buf.append(id); 219 buf.append(" {"); 220 buf.append(sep); 221 buf.append("measure="); 222 buf.append( 223 measure.getExpression() == null 224 ? measure.getAggregator().getExpression("*") 225 : measure.getAggregator().getExpression( 226 measure.getExpression().getGenericExpression())); 227 return buf.toString(); 228 } 229 230 public String toString() { 231 if (this.desc == null) { 232 StringBuilder buf = new StringBuilder(64); 233 describe(buf, false); 234 this.desc = buf.toString(); 235 } 236 return this.desc; 237 } 238 239 /** 240 * Returns whether a cell value is excluded from this segment. 241 */ 242 protected final boolean isExcluded(Object[] keys) { 243 // Performance critical: cannot use foreach 244 final int n = excludedRegions.size(); 245 //noinspection ForLoopReplaceableByForEach 246 for (int i = 0; i < n; i++) { 247 ExcludedRegion excludedRegion = excludedRegions.get(i); 248 if (excludedRegion.wouldContain(keys)) { 249 return true; 250 } 251 } 252 return false; 253 } 254 255 /** 256 * Prints the state of this <code>Segment</code>, including constraints 257 * and values. Blocks the current thread until the segment is loaded. 258 * 259 * @param pw Writer 260 */ 261 public void print(PrintWriter pw) { 262 final StringBuilder buf = new StringBuilder(); 263 describe(buf, true); 264 pw.print(buf.toString()); 265 pw.println(); 266 } 267 268 public List<ExcludedRegion> getExcludedRegions() { 269 return excludedRegions; 270 } 271 272 SegmentDataset createDataset( 273 SegmentAxis[] axes, 274 boolean sparse, 275 SqlStatement.Type type, 276 int size) 277 { 278 if (sparse) { 279 return new SparseSegmentDataset(); 280 } else { 281 switch (type) { 282 case OBJECT: 283 case LONG: 284 case STRING: 285 return new DenseObjectSegmentDataset(axes, size); 286 case INT: 287 return new DenseIntSegmentDataset(axes, size); 288 case DOUBLE: 289 return new DenseDoubleSegmentDataset(axes, size); 290 default: 291 throw Util.unexpected(type); 292 } 293 } 294 } 295 296 public boolean matches( 297 AggregationKey aggregationKey, 298 RolapStar.Measure measure) 299 { 300 // Perform high-selectivity comparisons first. 301 return aggregationKeyHashCode == aggregationKey.hashCode() 302 && this.measure == measure 303 && matchesInternal(aggregationKey); 304 } 305 306 private boolean matchesInternal(AggregationKey aggKey) { 307 return 308 constrainedColumnsBitKey.equals( 309 aggKey.getConstrainedColumnsBitKey()) 310 && star.equals(aggKey.getStar()) 311 && AggregationKey.equal( 312 compoundPredicateList, 313 aggKey.compoundPredicateList); 314 } 315 316 /** 317 * Definition of a region of values which are not in a segment. 318 */ 319 public static interface ExcludedRegion { 320 /** 321 * Tells whether this exclusion region would contain 322 * the cell corresponding to the keys. 323 */ 324 public boolean wouldContain(Object[] keys); 325 326 /** 327 * Returns the arity of this region. 328 */ 329 public int getArity(); 330 331 /** 332 * Describes this exclusion region in a human readable way. 333 */ 334 public void describe(StringBuilder buf); 335 336 /** 337 * Returns an approximation of the number of cells exceluded 338 * in this region. 339 */ 340 public int getCellCount(); 341 } 342 343 public SegmentHeader getHeader() { 344 return this.segmentHeader; 345 } 346} 347 348// End Segment.java