001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/FastBatchingCellReader.java#64 $
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) 2004-2008 Julian Hyde and others
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    package mondrian.rolap;
011    
012    import mondrian.olap.*;
013    import mondrian.rolap.agg.*;
014    import mondrian.rolap.aggmatcher.AggGen;
015    import mondrian.rolap.aggmatcher.AggStar;
016    import mondrian.rolap.sql.SqlQuery;
017    
018    import org.apache.log4j.Logger;
019    import org.eigenbase.util.property.*;
020    import org.eigenbase.util.property.Property;
021    
022    import java.util.*;
023    
024    /**
025     * A <code>FastBatchingCellReader</code> doesn't really Read cells: when asked
026     * to look up the values of stored measures, it lies, and records the fact
027     * that the value was asked for.  Later, we can look over the values which
028     * are required, fetch them in an efficient way, and re-run the evaluation
029     * with a real evaluator.
030     *
031     * <p>NOTE: When it doesn't know the answer, it lies by returning an error
032     * object.  The calling code must be able to deal with that.</p>
033     *
034     * <p>This class tries to minimize the amount of storage needed to record the
035     * fact that a cell was requested.</p>
036     */
037    public class FastBatchingCellReader implements CellReader {
038    
039        private static final Logger LOGGER =
040            Logger.getLogger(FastBatchingCellReader.class);
041    
042        /**
043         * This static variable controls the generation of aggregate table sql.
044         */
045        private static boolean generateAggregateSql =
046                 MondrianProperties.instance().GenerateAggregateSql.get();
047    
048        static {
049            // Trigger is used to lookup and change the value of the
050            // variable that controls generating aggregate table sql.
051            // Using a trigger means we don't have to look up the property eveytime.
052            MondrianProperties.instance().GenerateAggregateSql.addTrigger(
053                    new TriggerBase(true) {
054                        public void execute(Property property, String value) {
055                            generateAggregateSql = property.booleanValue();
056                        }
057                    });
058        }
059    
060        private final RolapCube cube;
061        private final Map<AggregationKey, Batch> batches;
062    
063        /**
064         * Records the number of requests. The field is used for correctness: if
065         * the request count stays the same during an operation, you know that the
066         * FastBatchingCellReader has not told any lies during that operation, and
067         * therefore the result is true. The field is also useful for debugging.
068         */
069        private int requestCount;
070    
071        final AggregationManager aggMgr = AggregationManager.instance();
072    
073        private final RolapAggregationManager.PinSet pinnedSegments =
074            aggMgr.createPinSet();
075    
076        /**
077         * Indicates that the reader has given incorrect results.
078         */
079        private boolean dirty;
080    
081        public FastBatchingCellReader(RolapCube cube) {
082            assert cube != null;
083            this.cube = cube;
084            this.batches = new HashMap<AggregationKey, Batch>();
085        }
086    
087        public Object get(RolapEvaluator evaluator) {
088            final CellRequest request =
089                RolapAggregationManager.makeRequest(evaluator);
090    
091            if (request == null || request.isUnsatisfiable()) {
092                return Util.nullValue; // request not satisfiable.
093            }
094    
095            // Try to retrieve a cell and simultaneously pin the segment which
096            // contains it.
097            final Object o = aggMgr.getCellFromCache(request, pinnedSegments);
098    
099            if (o == Boolean.TRUE) {
100                // Aggregation is being loaded. (todo: Use better value, or
101                // throw special exception)
102                return RolapUtil.valueNotReadyException;
103            }
104            if (o != null) {
105                return o;
106            }
107            // if there is no such cell, record that we need to fetch it, and
108            // return 'error'
109            recordCellRequest(request);
110            return RolapUtil.valueNotReadyException;
111        }
112    
113        public int getMissCount() {
114            return requestCount;
115        }
116    
117        public final void recordCellRequest(CellRequest request) {
118            if (request.isUnsatisfiable()) {
119                return;
120            }
121            ++requestCount;
122    
123            final AggregationKey key = new AggregationKey(request);
124            Batch batch = batches.get(key);
125            if (batch == null) {
126                batch = new Batch(request);
127                batches.put(key, batch);
128    
129                if (LOGGER.isDebugEnabled()) {
130                    StringBuilder buf = new StringBuilder(100);
131                    buf.append("FastBatchingCellReader: bitkey=");
132                    buf.append(request.getConstrainedColumnsBitKey());
133                    buf.append(Util.nl);
134    
135                    final RolapStar.Column[] columns =
136                        request.getConstrainedColumns();
137                    for (RolapStar.Column column : columns) {
138                        buf.append("  ");
139                        buf.append(column);
140                        buf.append(Util.nl);
141                    }
142                    LOGGER.debug(buf.toString());
143                }
144            }
145            batch.add(request);
146        }
147    
148        /**
149         * Returns whether this reader has told a lie. This is the case if there
150         * are pending batches to load or if {@link #setDirty(boolean)} has been
151         * called.
152         */
153        public boolean isDirty() {
154            return dirty || !batches.isEmpty();
155        }
156    
157        boolean loadAggregations() {
158            return loadAggregations(null);
159        }
160    
161        /**
162         * Loads pending aggregations, if any.
163         *
164         * @param query the parent query object that initiated this
165         * call
166         *
167         * @return Whether any aggregations were loaded.
168         */
169        boolean loadAggregations(Query query) {
170            final long t1 = System.currentTimeMillis();
171            if (!isDirty()) {
172                return false;
173            }
174    
175            // Sort the batches into deterministic order.
176            List<Batch> batchList = new ArrayList<Batch>(batches.values());
177            Collections.sort(batchList, BatchComparator.instance);
178            if (shouldUseGroupingFunction()) {
179                LOGGER.debug("Using grouping sets");
180                List<CompositeBatch> groupedBatches = groupBatches(batchList);
181                for (CompositeBatch batch : groupedBatches) {
182                    loadAggregation(query, batch);
183                }
184            } else {
185                // Load batches in turn.
186                for (Batch batch : batchList) {
187                    loadAggregation(query, batch);
188                }
189            }
190    
191            batches.clear();
192            dirty = false;
193    
194            if (LOGGER.isDebugEnabled()) {
195                final long t2 = System.currentTimeMillis();
196                LOGGER.debug("loadAggregation (millis): " + (t2 - t1));
197            }
198    
199            return true;
200        }
201    
202        private void loadAggregation(Query query, Loadable batch) {
203            if (query != null) {
204                query.checkCancelOrTimeout();
205            }
206            batch.loadAggregation();
207        }
208    
209        List<CompositeBatch> groupBatches(List<Batch> batchList) {
210            Map<AggregationKey, CompositeBatch> batchGroups =
211                new HashMap<AggregationKey, CompositeBatch>();
212            for (int i = 0; i < batchList.size(); i++) {
213                for (int j = i + 1; j < batchList.size() && i < batchList.size();) {
214                    FastBatchingCellReader.Batch iBatch = batchList.get(i);
215                    FastBatchingCellReader.Batch jBatch = batchList.get(j);
216                    if (iBatch.canBatch(jBatch)) {
217                        batchList.remove(j);
218                        addToCompositeBatch(batchGroups, iBatch, jBatch);
219                    } else if (jBatch.canBatch(iBatch)) {
220                        batchList.set(i, jBatch);
221                        batchList.remove(j);
222                        addToCompositeBatch(batchGroups, jBatch, iBatch);
223                        j = i + 1;
224                    } else {
225                        j++;
226                    }
227                }
228            }
229    
230            wrapNonBatchedBatchesWithCompositeBatches(batchList, batchGroups);
231            final CompositeBatch[] compositeBatches =
232                batchGroups.values().toArray(
233                    new CompositeBatch[batchGroups.size()]);
234            Arrays.sort(compositeBatches, CompositeBatchComparator.instance);
235            return Arrays.asList(compositeBatches);
236        }
237    
238        private void wrapNonBatchedBatchesWithCompositeBatches(
239            List<Batch> batchList,
240            Map<AggregationKey, CompositeBatch> batchGroups)
241        {
242            for (Batch batch : batchList) {
243                if (batchGroups.get(batch.batchKey) == null) {
244                    batchGroups.put(batch.batchKey, new CompositeBatch(batch));
245                }
246            }
247        }
248    
249    
250        void addToCompositeBatch(
251            Map<AggregationKey, CompositeBatch> batchGroups,
252            Batch detailedBatch,
253            Batch summaryBatch)
254        {
255            CompositeBatch compositeBatch = batchGroups.get(detailedBatch.batchKey);
256    
257            if (compositeBatch == null) {
258                compositeBatch = new CompositeBatch(detailedBatch);
259                batchGroups.put(detailedBatch.batchKey, compositeBatch);
260            }
261    
262            FastBatchingCellReader.CompositeBatch compositeBatchOfSummaryBatch =
263                batchGroups.remove(summaryBatch.batchKey);
264    
265            if (compositeBatchOfSummaryBatch != null) {
266                compositeBatch.merge(compositeBatchOfSummaryBatch);
267            } else {
268                compositeBatch.add(summaryBatch);
269            }
270    
271        }
272    
273        boolean shouldUseGroupingFunction() {
274            return MondrianProperties.instance().EnableGroupingSets.get() &&
275                doesDBSupportGroupingSets();
276        }
277    
278        /**
279         * Uses Dialect to identify if grouping sets is supported by the
280         * database.
281         */
282        boolean doesDBSupportGroupingSets() {
283            return getDialect().supportsGroupingSets();
284        }
285    
286        /**
287         * Returns the SQL dialect. Overridden in some unit tests.
288         *
289         * @return Dialect
290         */
291        SqlQuery.Dialect getDialect() {
292            final RolapStar star = cube.getStar();
293            if (star != null) {
294                return star.getSqlQueryDialect();
295            } else {
296                return cube.getSchema().getDialect();
297            }
298        }
299    
300        /**
301         * Sets the flag indicating that the reader has told a lie.
302         */
303        void setDirty(boolean dirty) {
304            this.dirty = dirty;
305        }
306    
307        /**
308         * Set of Batches which can grouped together.
309         */
310        class CompositeBatch implements Loadable {
311            /** Batch with most number of constraint columns */
312            final Batch detailedBatch;
313    
314            /** Batches whose data can be fetched using rollup on detailed batch */
315            final List<Batch> summaryBatches = new ArrayList<Batch>();
316    
317            CompositeBatch(Batch detailedBatch) {
318                this.detailedBatch = detailedBatch;
319            }
320    
321            void add(Batch summaryBatch) {
322                summaryBatches.add(summaryBatch);
323            }
324    
325            void merge(CompositeBatch summaryBatch) {
326                summaryBatches.add(summaryBatch.detailedBatch);
327                summaryBatches.addAll(summaryBatch.summaryBatches);
328            }
329    
330            public void loadAggregation() {
331                GroupingSetsCollector batchCollector =
332                    new GroupingSetsCollector(true);
333                this.detailedBatch.loadAggregation(batchCollector);
334    
335                for (Batch batch : summaryBatches) {
336                    batch.loadAggregation(batchCollector);
337                }
338    
339                getSegmentLoader().load(
340                    batchCollector.getGroupingSets(),
341                    pinnedSegments,
342                    detailedBatch.batchKey.getCompoundPredicateList());
343            }
344    
345            SegmentLoader getSegmentLoader() {
346                return new SegmentLoader();
347            }
348        }
349    
350        private static final Logger BATCH_LOGGER = Logger.getLogger(Batch.class);
351    
352        /**
353         * Encapsulates a common property of {@link Batch} and
354         * {@link CompositeBatch}, namely, that they can be asked to load their
355         * aggregations into the cache.
356         */
357        interface Loadable {
358            void loadAggregation();
359        }
360    
361        class Batch implements Loadable {
362            // the CellRequest's constrained columns
363            final RolapStar.Column[] columns;
364            final List<RolapStar.Measure> measuresList =
365                new ArrayList<RolapStar.Measure>();
366            final Set<StarColumnPredicate>[] valueSets;
367            final AggregationKey batchKey;
368            // string representation; for debug; set lazily in toString
369            private String string;
370    
371            public Batch(CellRequest request) {
372                columns = request.getConstrainedColumns();
373                valueSets = new HashSet[columns.length];
374                for (int i = 0; i < valueSets.length; i++) {
375                    valueSets[i] = new HashSet<StarColumnPredicate>();
376                }
377                batchKey = new AggregationKey(request);
378            }
379    
380            public String toString() {
381                if (string == null) {
382                    final StringBuilder buf = new StringBuilder();
383                    buf.append("Batch {\n")
384                        .append("  columns={").append(Arrays.asList(columns))
385                        .append("}\n")
386                        .append("  measures={").append(measuresList).append("}\n")
387                        .append("  valueSets={").append(Arrays.asList(valueSets))
388                        .append("}\n")
389                        .append("  batchKey=").append(batchKey).append("}\n")
390                        .append("}");
391                    string = buf.toString();
392                }
393                return string;
394            }
395    
396            public final void add(CellRequest request) {
397                final List<StarColumnPredicate> values = request.getValueList();
398                for (int j = 0; j < columns.length; j++) {
399                    valueSets[j].add(values.get(j));
400                }
401                final RolapStar.Measure measure = request.getMeasure();
402                if (!measuresList.contains(measure)) {
403                    assert (measuresList.size() == 0) ||
404                            (measure.getStar() ==
405                            (measuresList.get(0)).getStar()):
406                            "Measure must belong to same star as other measures";
407                    measuresList.add(measure);
408                }
409            }
410    
411            /**
412             * Returns the RolapStar associated with the Batch's first Measure.
413             *
414             * <p>This method can only be called after the {@link #add} method has
415             * been called.
416             *
417             * @return the RolapStar associated with the Batch's first Measure
418             */
419            private RolapStar getStar() {
420                RolapStar.Measure measure = measuresList.get(0);
421                return measure.getStar();
422            }
423    
424            public BitKey getConstrainedColumnsBitKey() {
425                return batchKey.getConstrainedColumnsBitKey();
426            }
427    
428            public final void loadAggregation() {
429                GroupingSetsCollector collectorWithGroupingSetsTurnedOff =
430                    new GroupingSetsCollector(false);
431                loadAggregation(collectorWithGroupingSetsTurnedOff);
432            }
433    
434            final void loadAggregation(
435                GroupingSetsCollector groupingSetsCollector)
436            {
437                if (generateAggregateSql) {
438                    generateAggregateSql();
439                }
440                final StarColumnPredicate[] predicates = initPredicates();
441                final long t1 = System.currentTimeMillis();
442    
443                final AggregationManager aggmgr = AggregationManager.instance();
444    
445                // TODO: optimize key sets; drop a constraint if more than x% of
446                // the members are requested; whether we should get just the cells
447                // requested or expand to a n-cube
448    
449                // If the database cannot execute "count(distinct ...)", split the
450                // distinct aggregations out.
451                final SqlQuery.Dialect dialect = getDialect();
452    
453                int distinctMeasureCount = getDistinctMeasureCount(measuresList);
454                boolean tooManyDistinctMeasures =
455                    distinctMeasureCount > 0 &&
456                    !dialect.allowsCountDistinct() ||
457                    distinctMeasureCount > 1 &&
458                    !dialect.allowsMultipleCountDistinct();
459    
460                if (tooManyDistinctMeasures) {
461                    doSpecialHandlingOfDistinctCountMeasures(
462                        aggmgr, predicates, groupingSetsCollector);
463                }
464    
465                // Load agg(distinct <SQL expression>) measures individually
466                // for DBs that does allow multiple distinct SQL measures.
467                if (!dialect.allowsMultipleDistinctSqlMeasures()) {
468    
469                    // Note that the intention was orignially to capture the
470                    // subquery SQL measures and separate them out; However,
471                    // without parsing the SQL string, Mondrian cannot distinguish
472                    // between "col1" + "col2" and subquery. Here the measure list
473                    // contains both types.
474    
475                    // See the test case testLoadDistinctSqlMeasure() in
476                    //  mondrian.rolap.FastBatchingCellReaderTest
477    
478                    List<RolapStar.Measure> distinctSqlMeasureList =
479                        getDistinctSqlMeasures(measuresList);
480                    for (RolapStar.Measure measure : distinctSqlMeasureList) {
481                        RolapStar.Measure[] measures = {measure};
482                        aggmgr.loadAggregation(
483                            measures, columns,
484                            batchKey,
485                            predicates,
486                            pinnedSegments, groupingSetsCollector);
487                        measuresList.remove(measure);
488                    }
489                }
490    
491                final int measureCount = measuresList.size();
492                if (measureCount > 0) {
493                    final RolapStar.Measure[] measures =
494                        measuresList.toArray(new RolapStar.Measure[measureCount]);
495                        aggmgr.loadAggregation(
496                            measures, columns,
497                            batchKey,
498                            predicates,
499                            pinnedSegments, groupingSetsCollector);
500                }
501    
502                if (BATCH_LOGGER.isDebugEnabled()) {
503                    final long t2 = System.currentTimeMillis();
504                    BATCH_LOGGER.debug(
505                        "Batch.loadAggregation (millis) " + (t2 - t1));
506                }
507            }
508    
509            private void doSpecialHandlingOfDistinctCountMeasures(
510                AggregationManager aggmgr,
511                StarColumnPredicate[] predicates,
512                GroupingSetsCollector groupingSetsCollector)
513            {
514                while (true) {
515                    // Scan for a measure based upon a distinct aggregation.
516                    final RolapStar.Measure distinctMeasure =
517                        getFirstDistinctMeasure(measuresList);
518                    if (distinctMeasure == null) {
519                        break;
520                    }
521                    final String expr =
522                        distinctMeasure.getExpression().getGenericExpression();
523                    final List<RolapStar.Measure> distinctMeasuresList =
524                        new ArrayList<RolapStar.Measure>();
525                    for (int i = 0; i < measuresList.size();) {
526                        final RolapStar.Measure measure = measuresList.get(i);
527                        if (measure.getAggregator().isDistinct() &&
528                            measure.getExpression().getGenericExpression().
529                                equals(expr)) {
530                            measuresList.remove(i);
531                            distinctMeasuresList.add(distinctMeasure);
532                        } else {
533                            i++;
534                        }
535                    }
536    
537                    // Load all the distinct measures based on the same expression
538                    // together
539                    final RolapStar.Measure[] measures =
540                        distinctMeasuresList.toArray(
541                            new RolapStar.Measure[distinctMeasuresList.size()]);
542                    aggmgr.loadAggregation(
543                        measures, columns,
544                        batchKey,
545                        predicates,
546                        pinnedSegments, groupingSetsCollector);
547                }
548            }
549    
550            private StarColumnPredicate[] initPredicates() {
551                StarColumnPredicate[] predicates =
552                        new StarColumnPredicate[columns.length];
553                for (int j = 0; j < columns.length; j++) {
554                    Set<StarColumnPredicate> valueSet = valueSets[j];
555    
556                    StarColumnPredicate predicate;
557                    if (valueSet == null) {
558                        predicate = LiteralStarPredicate.FALSE;
559                    } else {
560                        ValueColumnPredicate[] values =
561                            valueSet.toArray(
562                                new ValueColumnPredicate[valueSet.size()]);
563                        // Sort array to achieve determinism in generated SQL.
564                        Arrays.sort(
565                            values,
566                            ValueColumnConstraintComparator.instance);
567    
568                        predicate =
569                            new ListColumnPredicate(
570                                columns[j],
571                                Arrays.asList((StarColumnPredicate[]) values));
572                    }
573    
574                    predicates[j] = predicate;
575                }
576                return predicates;
577            }
578    
579            private void generateAggregateSql() {
580                final RolapCube cube = FastBatchingCellReader.this.cube;
581                if (cube == null || cube.isVirtual()) {
582                    final StringBuilder buf = new StringBuilder(64);
583                    buf.append("AggGen: Sorry, can not create SQL for virtual Cube \"");
584                    buf.append(cube.getName());
585                    buf.append("\", operation not currently supported");
586                    BATCH_LOGGER.error(buf.toString());
587    
588                } else {
589                    final AggGen aggGen =
590                        new AggGen(cube.getName(), cube.getStar(), columns);
591                    if (aggGen.isReady()) {
592                        // PRINT TO STDOUT - DO NOT USE BATCH_LOGGER
593                        System.out.println("createLost:" +
594                            Util.nl + aggGen.createLost());
595                        System.out.println("insertIntoLost:" +
596                            Util.nl + aggGen.insertIntoLost());
597                        System.out.println("createCollapsed:" +
598                            Util.nl + aggGen.createCollapsed());
599                        System.out.println("insertIntoCollapsed:" +
600                            Util.nl + aggGen.insertIntoCollapsed());
601                    } else {
602                        BATCH_LOGGER.error("AggGen failed");
603                    }
604                }
605            }
606    
607            /**
608             * Returns the first measure based upon a distinct aggregation, or null
609             * if there is none.
610             */
611            final RolapStar.Measure getFirstDistinctMeasure(
612                List<RolapStar.Measure> measuresList)
613            {
614                for (RolapStar.Measure measure : measuresList) {
615                    if (measure.getAggregator().isDistinct()) {
616                        return measure;
617                    }
618                }
619                return null;
620            }
621    
622            /**
623             * Returns the number of the measures based upon a distinct
624             * aggregation.
625             */
626            private int getDistinctMeasureCount(
627                List<RolapStar.Measure> measuresList)
628            {
629                int count = 0;
630                for (RolapStar.Measure measure : measuresList) {
631                    if (measure.getAggregator().isDistinct()) {
632                        ++count;
633                    }
634                }
635                return count;
636            }
637    
638            /**
639             * Returns the list of measures based upon a distinct aggregation
640             * containing SQL measure expressions(as opposed to column expressions).
641             *
642             * This method was initially intended for only those measures that are
643             * defined using subqueries(for DBs that support them). However, since
644             * Mondrian does not parse the SQL string, the method will count both
645             * queries as well as some non query SQL expressions.
646             */
647            private List<RolapStar.Measure> getDistinctSqlMeasures(
648                List<RolapStar.Measure> measuresList)
649            {
650                List<RolapStar.Measure> distinctSqlMeasureList =
651                    new ArrayList<RolapStar.Measure>();
652                for (RolapStar.Measure measure : measuresList) {
653                    if (measure.getAggregator().isDistinct() &&
654                        measure.getExpression() instanceof
655                            MondrianDef.MeasureExpression) {
656                        MondrianDef.MeasureExpression measureExpr =
657                            (MondrianDef.MeasureExpression) measure.getExpression();
658                        MondrianDef.SQL measureSql = measureExpr.expressions[0];
659                        // Checks if the SQL contains "SELECT" to detect the case a
660                        // subquery is used to define the measure. This is not a
661                        // perfect check, because a SQL expression on column names
662                        // containing "SELECT" will also be detected. e,g,
663                        // count("select beef" + "regular beef").
664                        if (measureSql.cdata.toUpperCase().contains("SELECT")) {
665                            distinctSqlMeasureList.add(measure);
666                        }
667                    }
668                }
669                return distinctSqlMeasureList;
670            }
671    
672            /**
673             * Returns whether another Batch can be batched to this Batch.
674             *
675             * <p>This is possible if:
676             * <li>columns list is super set of other batch's constraint columns;
677             *     and
678             * <li>both have same Fact Table; and
679             * <li>matching columns of this and other batch has the same value; and
680             * <li>non matching columns of this batch have ALL VALUES
681             * </ul>
682             */
683            boolean canBatch(Batch other) {
684                return hasOverlappingBitKeys(other)
685                    && constraintsMatch(other)
686                    && hasSameMeasureList(other)
687                    && haveSameStarAndAggregation(other);
688            }
689    
690            /**
691             * Returns whether the constraints on this Batch subsume the constraints
692             * on another Batch and therefore the other Batch can be subsumed into
693             * this one for GROUPING SETS purposes. Not symmetric.
694             *
695             * @param other Other batch
696             * @return Whether other batch can be subsumed into this one
697             */
698            private boolean constraintsMatch(Batch other) {
699                if (areBothDistinctCountBatches(other)) {
700                    if (getConstrainedColumnsBitKey().equals(
701                        other.getConstrainedColumnsBitKey())) {
702                        return hasSameCompoundPredicate(other)
703                            && haveSameValuesForOverlappingColumnsOrHasAllChildrenForOthers(other);
704                    } else {
705                        return hasSameCompoundPredicate(other)
706                            || (other.batchKey.getCompoundPredicateList().isEmpty()
707                                || equalConstraint(
708                                    batchKey.getCompoundPredicateList(),
709                                    other.batchKey.getCompoundPredicateList()))
710                            && haveSameValuesForOverlappingColumnsOrHasAllChildrenForOthers(other);
711                    }
712                } else {
713                    return haveSameValuesForOverlappingColumnsOrHasAllChildrenForOthers(other);
714                }
715            }
716    
717            private boolean equalConstraint(
718                List<StarPredicate> predList1,
719                List<StarPredicate> predList2)
720            {
721                if (predList1.size() != predList2.size()) {
722                    return false;
723                }
724                for (int i = 0; i < predList1.size(); i++) {
725                    StarPredicate pred1 = predList1.get(i);
726                    StarPredicate pred2 = predList2.get(i);
727                    if (!pred1.equalConstraint(pred2)) {
728                        return false;
729                    }
730                }
731                return true;
732            }
733    
734            private boolean areBothDistinctCountBatches(Batch other) {
735                return this.hasDistinctCountMeasure()
736                    && !this.hasNormalMeasures()
737                    && other.hasDistinctCountMeasure()
738                    && !other.hasNormalMeasures();
739            }
740    
741            private boolean hasNormalMeasures() {
742                return getDistinctMeasureCount(measuresList) !=  measuresList.size();
743            }
744    
745            private boolean hasSameMeasureList(Batch other) {
746                return (this.measuresList.size() == other.measuresList.size() &&
747                    this.measuresList.containsAll(other.measuresList));
748            }
749    
750            boolean hasOverlappingBitKeys(Batch other) {
751                return getConstrainedColumnsBitKey()
752                    .isSuperSetOf(other.getConstrainedColumnsBitKey());
753            }
754    
755            boolean hasDistinctCountMeasure() {
756                return getDistinctMeasureCount(measuresList) > 0;
757            }
758    
759            boolean hasSameCompoundPredicate(Batch other) {
760                final StarPredicate starPredicate = compoundPredicate();
761                final StarPredicate otherStarPredicate = other.compoundPredicate();
762                if (starPredicate == null && otherStarPredicate == null) {
763                    return true;
764                } else if (starPredicate != null && otherStarPredicate != null) {
765                    return starPredicate.equalConstraint(otherStarPredicate);
766                }
767                return false;
768            }
769    
770            private StarPredicate compoundPredicate() {
771                StarPredicate predicate = null;
772                for (Set<StarColumnPredicate> valueSet : valueSets) {
773                    StarPredicate orPredicate = null;
774                    for (StarColumnPredicate starColumnPredicate : valueSet) {
775                        if (orPredicate == null) {
776                            orPredicate = starColumnPredicate;
777                        } else {
778                            orPredicate = orPredicate.or(starColumnPredicate);
779                        }
780                    }
781                    if (predicate == null) {
782                        predicate = orPredicate;
783                    } else {
784                        predicate = predicate.and(orPredicate);
785                    }
786                }
787                for (StarPredicate starPredicate : this.batchKey.getCompoundPredicateList()) {
788                    if (predicate == null) {
789                        predicate = starPredicate;
790                    } else {
791                        predicate = predicate.and(starPredicate);
792                    }
793                }
794                return predicate;
795            }
796    
797            boolean haveSameStarAndAggregation(Batch other) {
798                boolean rollup[] = {false};
799                boolean otherRollup[] = {false};
800    
801                boolean hasSameAggregation = getAgg(rollup) == other.getAgg(otherRollup);
802                boolean hasSameRollupOption = rollup[0] == otherRollup[0];
803    
804                boolean hasSameStar = getStar().equals(other.getStar());
805                return hasSameStar && hasSameAggregation && hasSameRollupOption;
806            }
807    
808            /**
809             * @param rollup Out parameter
810             * @return AggStar
811             */
812            private AggStar getAgg(boolean[] rollup) {
813    
814                AggregationManager aggregationManager =
815                    AggregationManager.instance();
816                AggStar star =
817                    aggregationManager.findAgg(getStar(),
818                        getConstrainedColumnsBitKey(), makeMeasureBitKey(),
819                        rollup);
820                return star;
821            }
822    
823            private BitKey makeMeasureBitKey() {
824                BitKey bitKey = getConstrainedColumnsBitKey().emptyCopy();
825                for (RolapStar.Measure measure : measuresList) {
826                    bitKey.set(measure.getBitPosition());
827                }
828                return bitKey;
829            }
830    
831            boolean haveSameValuesForOverlappingColumnsOrHasAllChildrenForOthers(
832                Batch other)
833            {
834                for (int j = 0; j < columns.length; j++) {
835                    boolean isCommonColumn = false;
836                    for (int i = 0; i < other.columns.length; i++) {
837                        if (areSameColumns(other.columns[i], columns[j])) {
838                            if (hasSameValues(other.valueSets[i], valueSets[j])) {
839                                isCommonColumn = true;
840                                break;
841                            } else {
842                                return false;
843                            }
844                        }
845                    }
846                    if (!isCommonColumn &&
847                        !hasAllValues(columns[j], valueSets[j])) {
848                        return false;
849                    }
850                }
851                return true;
852            }
853    
854            private boolean hasAllValues(
855                RolapStar.Column column,
856                Set<StarColumnPredicate> valueSet)
857            {
858                return column.getCardinality() == valueSet.size();
859            }
860    
861            private boolean areSameColumns(
862                RolapStar.Column otherColumn,
863                RolapStar.Column thisColumn)
864            {
865                return otherColumn.equals(thisColumn);
866            }
867    
868            private boolean hasSameValues(
869                Set<StarColumnPredicate> otherValueSet,
870                Set<StarColumnPredicate> thisValueSet)
871            {
872                return otherValueSet.equals(thisValueSet);
873            }
874        }
875    
876        private static class CompositeBatchComparator
877            implements Comparator<CompositeBatch>
878        {
879            static final CompositeBatchComparator instance =
880                new CompositeBatchComparator();
881    
882            public int compare(CompositeBatch o1, CompositeBatch o2) {
883                return BatchComparator.instance.compare(
884                    o1.detailedBatch,
885                    o2.detailedBatch);
886            }
887        }
888    
889        private static class BatchComparator implements Comparator<Batch> {
890            static final BatchComparator instance = new BatchComparator();
891    
892            private BatchComparator() {
893            }
894    
895            public int compare(
896                Batch o1, Batch o2) {
897                if (o1.columns.length != o2.columns.length) {
898                    return o1.columns.length - o2.columns.length;
899                }
900                for (int i = 0; i < o1.columns.length; i++) {
901                    int c = o1.columns[i].getName().compareTo(
902                        o2.columns[i].getName());
903                    if (c != 0) {
904                        return c;
905                    }
906                }
907                for (int i = 0; i < o1.columns.length; i++) {
908                    int c = compare(o1.valueSets[i], o2.valueSets[i]);
909                    if (c != 0) {
910                        return c;
911                    }
912                }
913                return 0;
914            }
915    
916            <T> int compare(Set<T> set1, Set<T> set2) {
917                if (set1.size() != set2.size()) {
918                    return set1.size() - set2.size();
919                }
920                Iterator<T> iter1 = set1.iterator();
921                Iterator<T> iter2 = set2.iterator();
922                while (iter1.hasNext()) {
923                    T v1 = iter1.next();
924                    T v2 = iter2.next();
925                    int c = Util.compareKey(v1, v2);
926                    if (c != 0) {
927                        return c;
928                    }
929                }
930                return 0;
931            }
932        }
933    
934        private static class ValueColumnConstraintComparator
935            implements Comparator<ValueColumnPredicate>
936        {
937            static final ValueColumnConstraintComparator instance =
938                new ValueColumnConstraintComparator();
939    
940            private ValueColumnConstraintComparator() {
941            }
942    
943            public int compare(
944                ValueColumnPredicate o1,
945                ValueColumnPredicate o2)
946            {
947                Object v1 = o1.getValue();
948                Object v2 = o2.getValue();
949                if (v1.getClass() == v2.getClass() &&
950                    v1 instanceof Comparable) {
951                    return ((Comparable) v1).compareTo(v2);
952                } else {
953                    return v1.toString().compareTo(v2.toString());
954                }
955            }
956        }
957    }
958    
959    // End FastBatchingCellReader.java