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.rolap.*; 016 017import java.util.*; 018 019/** 020 * A <code>CellRequest</code> contains the context necessary to get a cell 021 * value from a star. 022 * 023 * @author jhyde 024 * @since 21 March, 2002 025 */ 026public class CellRequest { 027 private final RolapStar.Measure measure; 028 public final boolean extendedContext; 029 public final boolean drillThrough; 030 031 /* 032 * Sparsely populated array of column predicates. Each predicate will 033 * be located according to the bitPosition of the column to which it 034 * corresponds. This costs a little memory in terms of unused array 035 * slots, but avoids the need to explicitly sort the column predicates 036 * into a canonical order. There aren't usually a lot of predicates to 037 * sort, but that time adds up quickly. 038 */ 039 private StarColumnPredicate[] sparseColumnPredicateList; 040 041 /** 042 * An array that contains the bit positions of each constrained 043 * column. 044 * 045 * <p>Used to allow us to convert from a numeric index (i.e., give me the 046 * third constrained column) to the actual column as referenced 047 * by bit position. 048 * 049 * For example, if the three constrained columns have bitPositions 3, 16, 050 * and 21, the contents of this array should be [3,16,21]. 051 */ 052 private int[] columnBitPositions; 053 054 /** 055 * Tracks the number of column constraints actually associated with this 056 * CellRequest. We could figure this out by iterating over 057 * sparseColumnPredicateList, but it's quicker to just track them 058 * as they are added. 059 */ 060 private int numColumns; 061 062 /** 063 * Reference back to the CellRequest's star. All CellRequests in a 064 * given query are associated with a single star. Keeping this 065 * reference allows us to maintain a list of columns by bit position 066 * in just one place (the star) rather than duplicate that 067 * information in each CellRequest. 068 */ 069 private RolapStar star = null; 070 071 /* 072 * Array of column values; 073 * Not used to represent the compound members along one or more dimensions. 074 */ 075 private Object[] singleValues; 076 077 /** 078 * After all of the columns are loaded, the columnsCache is created 079 * the first time the getColumns method (or any method that itself 080 * calls the check method) is called. 081 * 082 * <p>It is assumed that the call to all additional columns, 083 * {@link #addConstrainedColumn}, will not be called after the first call 084 * to the {@link #getConstrainedColumns()} method. 085 * 086 * <p>TODO: Since the expectation is that the columns do not change once 087 * set, it may be worth either caching these structures so that only one 088 * exists for every unique combination, or eliminating the cache and 089 * creating a wrapper that makes the bit key + star's column list look 090 * like a column array. 091 */ 092 private RolapStar.Column[] columnsCache = null; 093 094 /** 095 * A bit is set for each column in the column list. Allows us to rapidly 096 * figure out whether two requests are for the same column set. 097 * These are all of the columns that are involved with a query, that is, all 098 * required to be present in an aggregate table for the table be used to 099 * fulfill the query. 100 */ 101 private final BitKey constrainedColumnsBitKey; 102 103 /** 104 * Map from BitKey (representing a group of columns that forms a 105 * compound key) to StarPredicate (representing the predicate 106 * defining the compound member). 107 * 108 * <p>We use LinkedHashMap so that the entries occur in deterministic 109 * order; otherwise, successive runs generate different SQL queries. 110 * Another solution worth considering would be to use the inherent ordering 111 * of BitKeys and create a sorted map. 112 * 113 * <p>Creating CellRequests is one of the top hotspots in Mondrian. 114 * Therefore we initialize the map to null, and don't create a map until 115 * we add the first entry. 116 * 117 * <p>The map (when not null) is sorted by key, to allow more rapid 118 * comparison with maps of other requests and with existing segments.</p> 119 */ 120 private SortedMap<BitKey, StarPredicate> compoundPredicateMap = null; 121 122 /** 123 * Whether the request is impossible to satisfy. This is set to 'true' if 124 * contradictory constraints are applied to the same column. For example, 125 * the levels [Customer].[City] and [Cities].[City] map to the same column 126 * via the same join-path, and one constraint sets city = 'Burbank' and 127 * another sets city = 'Los Angeles'. 128 */ 129 private boolean unsatisfiable; 130 131 /** 132 * The columnPredicateList and columnsCache must be set after all 133 * constraints have been added. This is used by access methods to determine 134 * if both columnPredicateList and columnsCache need to be generated. 135 */ 136 private boolean isDirty = true; 137 138 /** 139 * Creates a {@link CellRequest}. 140 * 141 * @param measure Measure the request is for 142 * @param extendedContext If a drill-through request, whether to join in 143 * unconstrained levels so as to display extra columns 144 * @param drillThrough Whether this is a request for a drill-through set 145 */ 146 public CellRequest( 147 RolapStar.Measure measure, 148 boolean extendedContext, 149 boolean drillThrough) 150 { 151 this.measure = measure; 152 this.extendedContext = extendedContext; 153 this.drillThrough = drillThrough; 154 this.constrainedColumnsBitKey = 155 BitKey.Factory.makeBitKey(measure.getStar().getColumnCount()); 156 this.sparseColumnPredicateList = 157 new StarColumnPredicate[measure.getStar().getColumnCount()]; 158 } 159 160 /** 161 * Adds a constraint to this request. 162 * 163 * @param column Column to constraint 164 * @param predicate Constraint to apply, or null to add column to the 165 * output without applying constraint 166 */ 167 public final void addConstrainedColumn( 168 RolapStar.Column column, 169 StarColumnPredicate predicate) 170 { 171 assert columnsCache == null; 172 173 // Sanity check; we should never be adding column constraints 174 // from more than one star 175 if (star == null) { 176 star = column.getStar(); 177 } else { 178 assert (star == column.getStar()); 179 } 180 181 final int bitPosition = column.getBitPosition(); 182 if (this.constrainedColumnsBitKey.get(bitPosition)) { 183 // This column is already constrained. Unless the value is the 184 // same, or this value or the previous value is null (meaning 185 // unconstrained) the request will never return any results. 186 final StarColumnPredicate prevValue = 187 sparseColumnPredicateList[bitPosition]; 188 if (prevValue == null) { 189 // Previous column was unconstrained. Constrain on new 190 // value. 191 } else if (predicate == null) { 192 // Previous column was constrained. Nothing to do. 193 return; 194 } else if (predicate.equalConstraint(prevValue)) { 195 // Same constraint again. Nothing to do. 196 return; 197 } else { 198 // Different constraint. Request is impossible to satisfy. 199 predicate = null; 200 unsatisfiable = true; 201 } 202 } else { 203 this.constrainedColumnsBitKey.set(bitPosition); 204 numColumns++; 205 } 206 207 // Note: it is possible and valid for predicate to be null here 208 this.sparseColumnPredicateList[bitPosition] = predicate; 209 } 210 211 /** 212 * Add compound member (formed via aggregate function) constraint to the 213 * Cell. 214 * 215 * @param compoundBitKey Compound bit key 216 * @param compoundPredicate Compound predicate 217 */ 218 public void addAggregateList( 219 BitKey compoundBitKey, 220 StarPredicate compoundPredicate) 221 { 222 if (compoundPredicateMap == null) { 223 compoundPredicateMap = new TreeMap<BitKey, StarPredicate>(); 224 } 225 compoundPredicateMap.put(compoundBitKey, compoundPredicate); 226 } 227 228 /** 229 * Returns the measure of this cell request. 230 * 231 * @return Measure 232 */ 233 public RolapStar.Measure getMeasure() { 234 return measure; 235 } 236 237 public RolapStar.Column[] getConstrainedColumns() { 238 if (this.columnsCache == null) { 239 // This is called more than once so caching the value makes sense. 240 check(); 241 } 242 return this.columnsCache; 243 } 244 245 /** 246 * Returns the BitKey for the list of columns. 247 * 248 * @return BitKey for the list of columns 249 */ 250 public BitKey getConstrainedColumnsBitKey() { 251 return constrainedColumnsBitKey; 252 } 253 254 /** 255 * Returns the map of compound predicates, or null if empty. 256 * 257 * <p>NOTE: It is not generally considered good API design to return null 258 * to represent empty collections, but this collection is very often empty 259 * and the the implementation of Collections.emptyMap().keySet().iterator() 260 * is slow, so we optimize for the common case. 261 * 262 * @return predicate map, or null if empty 263 */ 264 SortedMap<BitKey, StarPredicate> getCompoundPredicateMap() { 265 return compoundPredicateMap; 266 } 267 268 /** 269 * Builds the {@link #columnsCache} and {@link #columnBitPositions} 270 * based upon bit key position of the columns. 271 */ 272 private void check() { 273 if (isDirty) { 274 columnsCache = new RolapStar.Column[numColumns]; 275 columnBitPositions = new int[numColumns]; 276 int i = 0; 277 for (int bitPos = constrainedColumnsBitKey.nextSetBit(0); 278 bitPos >= 0; 279 bitPos = constrainedColumnsBitKey.nextSetBit(bitPos + 1)) 280 { 281 columnBitPositions[i] = bitPos; 282 columnsCache[i] = this.star.getColumn(bitPos); 283 i++; 284 } 285 isDirty = false; 286 } 287 } 288 289 /** 290 * Return the predicate value associated with the given index. Note that 291 * index is different than bit position; if there are three constraints then 292 * the indices are 0, 1, and 2, while the bitPositions could span a larger 293 * range. 294 * 295 * <p> It is valid for the predicate at a given index to be null (there 296 * should always be a column at that index, but it may not have an 297 * associated predicate). 298 * 299 * @param index Index of the constraint we're looking up 300 * @return predicate value associated with the given index 301 */ 302 public StarColumnPredicate getValueAt(int index) { 303 check(); 304 return sparseColumnPredicateList[columnBitPositions[index]]; 305 } 306 307 /** 308 * Return the number of column constraints associated with this CellRequest. 309 * 310 * @return number of columns in the CellRequest 311 */ 312 public int getNumValues() { 313 check(); 314 return numColumns; 315 } 316 317 /** 318 * Returns an array of the values for each column. 319 * 320 * <p>The caller must check whether this request is satisfiable before 321 * calling this method. May throw {@link NullPointerException} if request 322 * is not satisfiable. 323 * 324 * @pre !isUnsatisfiable() 325 * @return Array of values for each column 326 */ 327 public Object[] getSingleValues() { 328 assert !unsatisfiable; 329 if (singleValues == null) { 330 check(); 331 singleValues = new Object[numColumns]; 332 int i = 0; 333 for (int bitPos : columnBitPositions) { 334 ValueColumnPredicate predicate = 335 (ValueColumnPredicate) sparseColumnPredicateList[bitPos]; 336 singleValues[i++] = predicate.getValue(); 337 } 338 } 339 return singleValues; 340 } 341 342 /** 343 * Builds a map of column names to values, as specified 344 * by this cell request object. 345 */ 346 public Map<String, Comparable> getMappedCellValues() { 347 final Map<String, Comparable> map = 348 new HashMap<String, Comparable>(); 349 final RolapStar.Column[] columns = 350 this.getConstrainedColumns(); 351 final Object[] values = this.getSingleValues(); 352 for (int i = 0; i < columns.length; i++) { 353 RolapStar.Column column = columns[i]; 354 final Object o = values[i]; 355 map.put( 356 column.getExpression().getGenericExpression(), 357 (Comparable) o); 358 } 359 return map; 360 } 361 362 /** 363 * Returns whether this cell request is impossible to satisfy. 364 * This occurs when the same column has two or more inconsistent 365 * constraints. 366 * 367 * @return whether this cell request is impossible to satisfy 368 */ 369 public boolean isUnsatisfiable() { 370 return unsatisfiable; 371 } 372} 373 374// End CellRequest.java