001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/agg/CellRequest.java#26 $ 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 014 package mondrian.rolap.agg; 015 016 import mondrian.rolap.*; 017 018 import java.util.*; 019 020 /** 021 * A <code>CellRequest</code> contains the context necessary to get a cell 022 * value from a star. 023 * 024 * @author jhyde 025 * @since 21 March, 2002 026 * @version $Id: //open/mondrian/src/main/mondrian/rolap/agg/CellRequest.java#26 $ 027 */ 028 public class CellRequest { 029 private final RolapStar.Measure measure; 030 public final boolean extendedContext; 031 public final boolean drillThrough; 032 /** 033 * List of {@link mondrian.rolap.RolapStar.Column}s being which have values 034 * in this request. 035 */ 036 private final List<RolapStar.Column> constrainedColumnList = 037 new ArrayList<RolapStar.Column>(); 038 039 private final List<StarColumnPredicate> columnPredicateList = 040 new ArrayList<StarColumnPredicate>(); 041 042 /* 043 * Array of column values; 044 * Not used to represent the compound members along one or more dimensions. 045 */ 046 private Object[] singleValues; 047 048 /** 049 * After all of the columns are loaded into the constrainedColumnList instance 050 * variable, this columnsCache is created the first time the getColumns 051 * method is called. 052 * <p> 053 * It is assumed that the call to all additional columns, 054 * {@link #addConstrainedColumn}, will not be called after the first call to 055 * the {@link #getConstrainedColumns()} method. 056 */ 057 private RolapStar.Column[] columnsCache = null; 058 059 /** 060 * A bit is set for each column in the column list. Allows us to rapidly 061 * figure out whether two requests are for the same column set. 062 * These are all of the columns that are involved with a query, that is, all 063 * required to be present in an aggregate table for the table be used to 064 * fulfill the query. 065 */ 066 private final BitKey constrainedColumnsBitKey; 067 068 /** 069 * Map from BitKey (representing a group of columns that forms a 070 * compound key) to StarPredicate (representing the predicate 071 * defining the compound member). 072 * 073 * <p>We use LinkedHashMap so that the entries occur in deterministic 074 * order; otherwise, successive runs generate different SQL queries. 075 * Another solution worth considering would be to use the inherent ordering 076 * of BitKeys and create a sorted map. 077 */ 078 private final Map<BitKey, StarPredicate> compoundPredicateMap = 079 new LinkedHashMap<BitKey, StarPredicate>(); 080 081 /** 082 * Whether the request is impossible to satisfy. This is set to 'true' if 083 * contradictory constraints are applied to the same column. For example, 084 * the levels [Customer].[City] and [Cities].[City] map to the same column 085 * via the same join-path, and one constraint sets city = 'Burbank' and 086 * another sets city = 'Los Angeles'. 087 */ 088 private boolean unsatisfiable; 089 090 /** 091 * The columnPredicateList and columnsCache must be set after all constraints 092 * have been added. This is used by access methods to determine if 093 * both columnPredicateList and columnsCache need to be generated. 094 */ 095 private boolean isDirty = true; 096 097 /** 098 * Creates a {@link CellRequest}. 099 * 100 * @param measure Measure the request is for 101 * @param extendedContext If a drill-through request, whether to join in 102 * unconstrained levels so as to display extra columns 103 * @param drillThrough Whether this is a request for a drill-through set 104 */ 105 public CellRequest( 106 RolapStar.Measure measure, 107 boolean extendedContext, 108 boolean drillThrough) 109 { 110 this.measure = measure; 111 this.extendedContext = extendedContext; 112 this.drillThrough = drillThrough; 113 this.constrainedColumnsBitKey = 114 BitKey.Factory.makeBitKey(measure.getStar().getColumnCount()); 115 } 116 117 /** 118 * Adds a constraint to this request. 119 * 120 * @param column Column to constraint 121 * @param predicate Constraint to apply, or null to add column to the 122 * output without applying constraint 123 */ 124 public final void addConstrainedColumn( 125 RolapStar.Column column, 126 StarColumnPredicate predicate) 127 { 128 assert columnsCache == null; 129 130 final int bitPosition = column.getBitPosition(); 131 if (this.constrainedColumnsBitKey.get(bitPosition)) { 132 // This column is already constrained. Unless the value is the same, 133 // or this value or the previous value is null (meaning 134 // unconstrained) the request will never return any results. 135 int index = constrainedColumnList.indexOf(column); 136 assert index >= 0; 137 final StarColumnPredicate prevValue = columnPredicateList.get(index); 138 if (prevValue == null) { 139 // Previous column was unconstrained. Constrain on new 140 // value. 141 } else if (predicate == null) { 142 // Previous column was constrained. Nothing to do. 143 return; 144 } else if (predicate.equalConstraint(prevValue)) { 145 // Same constraint again. Nothing to do. 146 return; 147 } else { 148 // Different constraint. Request is impossible to satisfy. 149 predicate = null; 150 unsatisfiable = true; 151 } 152 columnPredicateList.set(index, predicate); 153 154 } else { 155 this.constrainedColumnList.add(column); 156 this.constrainedColumnsBitKey.set(bitPosition); 157 this.columnPredicateList.add(predicate); 158 } 159 } 160 161 /** 162 * Add compound member (formed via aggregate function) constraint to the 163 * Cell. 164 * 165 * @param compoundBitKey 166 * @param compoundPredicate 167 */ 168 public void addAggregateList( 169 BitKey compoundBitKey, 170 StarPredicate compoundPredicate) 171 { 172 compoundPredicateMap.put(compoundBitKey, compoundPredicate); 173 } 174 175 public RolapStar.Measure getMeasure() { 176 return measure; 177 } 178 179 public RolapStar.Column[] getConstrainedColumns() { 180 if (this.columnsCache == null) { 181 // This is called more than once so caching the value makes 182 // sense. 183 check(); 184 } 185 return this.columnsCache; 186 } 187 188 /** 189 * Returns the BitKey for the list of columns. 190 * 191 * @return BitKey for the list of columns 192 */ 193 public BitKey getConstrainedColumnsBitKey() { 194 return constrainedColumnsBitKey; 195 } 196 197 /** 198 * Get the map of compound predicates 199 * @return predicate map 200 */ 201 public Map<BitKey, StarPredicate> getCompoundPredicateMap() { 202 return compoundPredicateMap; 203 } 204 205 /** 206 * Builds the {@link #columnsCache} and reorders the 207 * {@link #columnPredicateList} 208 * based upon bit key position of the columns. 209 */ 210 private void check() { 211 if (isDirty) { 212 final int size = constrainedColumnList.size(); 213 this.columnsCache = new RolapStar.Column[size]; 214 final StarColumnPredicate[] oldColumnPredicates = 215 columnPredicateList.toArray( 216 new StarColumnPredicate[columnPredicateList.size()]); 217 columnPredicateList.clear(); 218 int i = 0; 219 for (int bitPos : constrainedColumnsBitKey) { 220 // NOTE: If the RolapStar.Column were stored in maybe a Map 221 // rather than the constrainedColumnList List, we would 222 // not have to for-loop over the list for each bit position. 223 for (int j = 0; j < size; j++) { 224 RolapStar.Column rc = constrainedColumnList.get(j); 225 if (rc.getBitPosition() == bitPos) { 226 int index = constrainedColumnList.indexOf(rc); 227 final StarColumnPredicate value = 228 oldColumnPredicates[index]; 229 columnPredicateList.add(value); 230 columnsCache[i++] = rc; 231 break; 232 } 233 } 234 } 235 isDirty = false; 236 } 237 } 238 239 public List<StarColumnPredicate> getValueList() { 240 check(); 241 return columnPredicateList; 242 } 243 244 /** 245 * Returns an array of the values for each column. 246 * 247 * <p>The caller must check whether this request is satisfiable before 248 * calling this method. May throw {@link NullPointerException} if request 249 * is not satisfiable. 250 * 251 * @pre !isUnsatisfiable() 252 * @return Array of values for each column 253 */ 254 public Object[] getSingleValues() { 255 assert !unsatisfiable; 256 if (singleValues == null) { 257 check(); 258 singleValues = new Object[columnPredicateList.size()]; 259 final int size = columnPredicateList.size(); 260 for (int i = 0; i < size; i++) { 261 ValueColumnPredicate predicate = 262 (ValueColumnPredicate) columnPredicateList.get(i); 263 singleValues[i] = predicate.getValue(); 264 } 265 } 266 return singleValues; 267 } 268 269 /** 270 * Returns whether this cell request is impossible to satisfy. 271 * This occurs when the same column has two or more inconsistent 272 * constraints. 273 * 274 * @return whether this cell request is impossible to satisfy 275 */ 276 public boolean isUnsatisfiable() { 277 return unsatisfiable; 278 } 279 } 280 281 // End CellRequest.java