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