001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/agg/AggregationManager.java#61 $ 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-2002 Kana Software, Inc. 007 // Copyright (C) 2001-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, 30 August, 2001 012 */ 013 014 package mondrian.rolap.agg; 015 016 import mondrian.olap.MondrianProperties; 017 import mondrian.olap.Util; 018 import mondrian.rolap.*; 019 import mondrian.rolap.aggmatcher.AggStar; 020 021 import org.apache.log4j.Logger; 022 023 import java.util.*; 024 025 /** 026 * <code>RolapAggregationManager</code> manages all {@link Aggregation}s 027 * in the system. It is a singleton class. 028 * 029 * @author jhyde 030 * @since 30 August, 2001 031 * @version $Id: //open/mondrian/src/main/mondrian/rolap/agg/AggregationManager.java#61 $ 032 */ 033 public class AggregationManager extends RolapAggregationManager { 034 035 private static final MondrianProperties properties = 036 MondrianProperties.instance(); 037 038 private static final Logger LOGGER = 039 Logger.getLogger(AggregationManager.class); 040 041 private static AggregationManager instance; 042 043 /** Returns or creates the singleton. */ 044 public static synchronized AggregationManager instance() { 045 if (instance == null) { 046 if (properties.EnableCacheHitCounters.get()) { 047 instance = new CountingAggregationManager(); 048 } else { 049 instance = new AggregationManager(); 050 } 051 } 052 return instance; 053 } 054 055 AggregationManager() { 056 super(); 057 } 058 059 public Logger getLogger() { 060 return LOGGER; 061 } 062 063 /** 064 * Called by FastBatchingCellReader.loadAggregation where the 065 * RolapStar creates an Aggregation if needed. 066 * 067 * @param measures Measures to load 068 * @param columns this is the CellRequest's constrained columns 069 * @param aggregationKey this is the CellRequest's constraint key 070 * @param predicates Array of constraints on each column 071 * @param pinnedSegments Set of pinned segments 072 * @param groupingSetsCollector grouping sets collector 073 */ 074 public void loadAggregation( 075 RolapStar.Measure[] measures, 076 RolapStar.Column[] columns, 077 AggregationKey aggregationKey, 078 StarColumnPredicate[] predicates, 079 PinSet pinnedSegments, 080 GroupingSetsCollector groupingSetsCollector) 081 { 082 RolapStar star = measures[0].getStar(); 083 Aggregation aggregation = 084 star.lookupOrCreateAggregation(aggregationKey); 085 086 // try to eliminate unneccessary constraints 087 // for Oracle: prevent an IN-clause with more than 1000 elements 088 predicates = aggregation.optimizePredicates(columns, predicates); 089 aggregation.load( 090 columns, measures, predicates, pinnedSegments, 091 groupingSetsCollector); 092 } 093 094 public Object getCellFromCache(CellRequest request) { 095 return getCellFromCache(request, null); 096 } 097 098 public Object getCellFromCache(CellRequest request, PinSet pinSet) { 099 final RolapStar.Measure measure = request.getMeasure(); 100 // REVIEW: 101 // Is it possible to optimize this so not every cell lookup 102 // causes an AggregationKey to be created. 103 AggregationKey aggregationKey = new AggregationKey(request); 104 final Aggregation aggregation = 105 measure.getStar().lookupAggregation(aggregationKey); 106 107 if (aggregation == null) { 108 // cell is not in any aggregation 109 return null; 110 } else { 111 return aggregation.getCellValue( 112 measure, request.getSingleValues(), pinSet); 113 } 114 } 115 116 public String getDrillThroughSql( 117 final CellRequest request, 118 boolean countOnly) 119 { 120 DrillThroughQuerySpec spec = 121 new DrillThroughQuerySpec( 122 request, 123 countOnly); 124 String sql = spec.generateSqlQuery(); 125 126 if (getLogger().isDebugEnabled()) { 127 StringBuilder buf = new StringBuilder(256); 128 buf.append("DrillThroughSQL: "); 129 buf.append(sql); 130 buf.append(Util.nl); 131 getLogger().debug(buf.toString()); 132 } 133 134 return sql; 135 } 136 137 /** 138 * Generates the query to retrieve the cells for a list of segments. 139 * Called by Segment.load 140 */ 141 public String generateSql( 142 GroupingSetsList groupingSetsList, 143 List<StarPredicate> compoundPredicateList) 144 { 145 BitKey levelBitKey = groupingSetsList.getDefaultLevelBitKey(); 146 BitKey measureBitKey = groupingSetsList.getDefaultMeasureBitKey(); 147 148 // Check if using aggregates is enabled. 149 boolean hasCompoundPredicates = false; 150 if (compoundPredicateList != null && compoundPredicateList.size() > 0) { 151 // Do not use Aggregate tables if compound predicates are present. 152 hasCompoundPredicates = true; 153 } 154 if (MondrianProperties.instance().UseAggregates.get() && !hasCompoundPredicates) { 155 RolapStar star = groupingSetsList.getStar(); 156 157 final boolean[] rollup = {false}; 158 AggStar aggStar = findAgg(star, levelBitKey, measureBitKey, rollup); 159 160 if (aggStar != null) { 161 // Got a match, hot damn 162 163 if (getLogger().isDebugEnabled()) { 164 StringBuilder buf = new StringBuilder(256); 165 buf.append("MATCH: "); 166 buf.append(star.getFactTable().getAlias()); 167 buf.append(Util.nl); 168 buf.append(" foreign="); 169 buf.append(levelBitKey); 170 buf.append(Util.nl); 171 buf.append(" measure="); 172 buf.append(measureBitKey); 173 buf.append(Util.nl); 174 buf.append(" aggstar="); 175 buf.append(aggStar.getBitKey()); 176 buf.append(Util.nl); 177 buf.append("AggStar="); 178 buf.append(aggStar.getFactTable().getName()); 179 buf.append(Util.nl); 180 for (AggStar.Table.Column column : aggStar.getFactTable() 181 .getColumns()) { 182 buf.append(" "); 183 buf.append(column); 184 buf.append(Util.nl); 185 } 186 getLogger().debug(buf.toString()); 187 } 188 189 AggQuerySpec aggQuerySpec = 190 new AggQuerySpec(aggStar, rollup[0], 191 groupingSetsList); 192 String sql = aggQuerySpec.generateSqlQuery(); 193 194 if (getLogger().isDebugEnabled()) { 195 StringBuilder buf = new StringBuilder(256); 196 buf.append("generateSqlQuery: sql="); 197 buf.append(sql); 198 getLogger().debug(buf.toString()); 199 } 200 201 return sql; 202 } 203 204 // No match, fall through and use fact table. 205 } 206 207 if (getLogger().isDebugEnabled()) { 208 RolapStar star = groupingSetsList.getStar(); 209 210 StringBuilder buf = new StringBuilder(256); 211 buf.append("NO MATCH: "); 212 buf.append(star.getFactTable().getAlias()); 213 buf.append(Util.nl); 214 buf.append(" foreign="); 215 buf.append(levelBitKey); 216 buf.append(Util.nl); 217 buf.append(" measure="); 218 buf.append(measureBitKey); 219 buf.append(Util.nl); 220 221 getLogger().debug(buf.toString()); 222 } 223 224 225 // Fact table query 226 SegmentArrayQuerySpec spec = 227 new SegmentArrayQuerySpec(groupingSetsList, compoundPredicateList); 228 229 String sql = spec.generateSqlQuery(); 230 231 if (getLogger().isDebugEnabled()) { 232 StringBuilder buf = new StringBuilder(256); 233 buf.append("generateSqlQuery: sql="); 234 buf.append(sql); 235 getLogger().debug(buf.toString()); 236 } 237 238 return sql; 239 } 240 241 /** 242 * Finds an aggregate table in the given star which has the desired levels 243 * and measures. Returns null if no aggregate table is suitable. 244 * 245 * <p>If there no aggregate is an exact match, returns a more 246 * granular aggregate which can be rolled up, and sets rollup to true. 247 * If one or more of the measures are distinct-count measures 248 * rollup is possible only in limited circumstances. 249 * 250 * @param star Star 251 * @param levelBitKey Set of levels 252 * @param measureBitKey Set of measures 253 * @param rollup Out parameter, is set to true if the aggregate is not 254 * an exact match 255 * @return An aggregate, or null if none is suitable. 256 */ 257 public AggStar findAgg( 258 RolapStar star, 259 final BitKey levelBitKey, 260 final BitKey measureBitKey, 261 boolean[] rollup) { 262 // If there is no distinct count measure, isDistinct == false, 263 // then all we want is an AggStar whose BitKey is a superset 264 // of the combined measure BitKey and foreign-key/level BitKey. 265 // 266 // On the other hand, if there is at least one distinct count 267 // measure, isDistinct == true, then what is wanted is an AggStar 268 // whose measure BitKey is a superset of the measure BitKey, 269 // whose level BitKey is an exact match and the aggregate table 270 // can NOT have any foreign keys. 271 assert rollup != null; 272 BitKey fullBitKey = levelBitKey.or(measureBitKey); 273 274 // The AggStars are already ordered from smallest to largest so 275 // we need only find the first one and return it. 276 for (AggStar aggStar : star.getAggStars()) { 277 // superset match 278 if (!aggStar.superSetMatch(fullBitKey)) { 279 continue; 280 } 281 282 boolean isDistinct = measureBitKey.intersects( 283 aggStar.getDistinctMeasureBitKey()); 284 285 // The AggStar has no "distinct count" measures so 286 // we can use it without looking any further. 287 if (!isDistinct) { 288 rollup[0] = !aggStar.getLevelBitKey().equals(levelBitKey); 289 return aggStar; 290 } 291 292 // If there are distinct measures, we can only rollup in limited 293 // circumstances. 294 295 // No foreign keys (except when its used as a distinct count 296 // measure). 297 // Level key exact match. 298 // Measure superset match. 299 300 // Compute the core levels -- those which can be safely 301 // rolled up to. For example, 302 // if the measure is 'distinct customer count', 303 // and the agg table has levels customer_id, 304 // then gender is a core level. 305 final BitKey distinctMeasuresBitKey = 306 measureBitKey.and(aggStar.getDistinctMeasureBitKey()); 307 final BitSet distinctMeasures = distinctMeasuresBitKey.toBitSet(); 308 BitKey combinedLevelBitKey = null; 309 for (int k = distinctMeasures.nextSetBit(0); k >= 0; 310 k = distinctMeasures.nextSetBit(k + 1)) { 311 final AggStar.FactTable.Measure distinctMeasure = 312 aggStar.lookupMeasure(k); 313 BitKey rollableLevelBitKey = 314 distinctMeasure.getRollableLevelBitKey(); 315 if (combinedLevelBitKey == null) { 316 combinedLevelBitKey = rollableLevelBitKey; 317 } else { 318 // TODO use '&=' to remove unnecessary copy 319 combinedLevelBitKey = 320 combinedLevelBitKey.and(rollableLevelBitKey); 321 } 322 } 323 324 if (aggStar.hasForeignKeys()) { 325 /* 326 StringBuilder buf = new StringBuilder(256); 327 buf.append(""); 328 buf.append(star.getFactTable().getAlias()); 329 buf.append(Util.nl); 330 buf.append("foreign ="); 331 buf.append(levelBitKey); 332 buf.append(Util.nl); 333 buf.append("measure ="); 334 buf.append(measureBitKey); 335 buf.append(Util.nl); 336 buf.append("aggstar ="); 337 buf.append(aggStar.getBitKey()); 338 buf.append(Util.nl); 339 buf.append("distinct="); 340 buf.append(aggStar.getDistinctMeasureBitKey()); 341 buf.append(Util.nl); 342 buf.append("AggStar="); 343 buf.append(aggStar.getFactTable().getName()); 344 buf.append(Util.nl); 345 for (Iterator columnIter = 346 aggStar.getFactTable().getColumns().iterator(); 347 columnIter.hasNext();) { 348 AggStar.Table.Column column = 349 (AggStar.Table.Column) columnIter.next(); 350 buf.append(" "); 351 buf.append(column); 352 buf.append(Util.nl); 353 } 354 System.out.println(buf.toString()); 355 */ 356 // This is a little pessimistic. If the measure is 357 // 'count(distinct customer_id)' and one of the foreign keys is 358 // 'customer_id' then it is OK to roll up. 359 360 // Some of the measures in this query are distinct count. 361 // Get all of the foreign key columns. 362 // For each such measure, is it based upon a foreign key. 363 // Are there any foreign keys left over. No, can use AggStar. 364 BitKey fkBitKey = aggStar.getForeignKeyBitKey().copy(); 365 for (AggStar.FactTable.Measure measure : aggStar.getFactTable() 366 .getMeasures()) { 367 if (measure.isDistinct()) { 368 if (measureBitKey.get(measure.getBitPosition())) { 369 fkBitKey.clear(measure.getBitPosition()); 370 } 371 } 372 } 373 if (!fkBitKey.isEmpty()) { 374 // there are foreign keys left so we can not use this 375 // AggStar. 376 continue; 377 } 378 } 379 380 if (!aggStar.select( 381 levelBitKey, combinedLevelBitKey, measureBitKey)) { 382 continue; 383 } 384 385 rollup[0] = !aggStar.getLevelBitKey().equals(levelBitKey); 386 return aggStar; 387 } 388 return null; 389 } 390 391 public PinSet createPinSet() { 392 return new PinSetImpl(); 393 } 394 395 /** 396 * Implementation of {@link mondrian.rolap.RolapAggregationManager.PinSet} 397 * using a {@link HashSet}. 398 */ 399 public static class PinSetImpl 400 extends HashSet<Segment> 401 implements RolapAggregationManager.PinSet { 402 } 403 } 404 405 // End AggregationManager.java