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.olap.Util;
016import mondrian.rolap.*;
017import mondrian.spi.SegmentHeader;
018
019import org.apache.log4j.Logger;
020
021import java.io.PrintWriter;
022import java.util.*;
023
024/**
025 * A <code>Segment</code> is a collection of cell values parameterized by
026 * a measure, and a set of (column, value) pairs. An example of a segment is</p>
027 *
028 * <blockquote>
029 *   <p>(Unit sales, Gender = 'F', State in {'CA','OR'}, Marital Status = <i>
030 *   anything</i>)</p>
031 * </blockquote>
032 *
033 * <p>All segments over the same set of columns belong to an Aggregation, in
034 * this case:</p>
035 *
036 * <blockquote>
037 *   <p>('Sales' Star, Gender, State, Marital Status)</p>
038 * </blockquote>
039 *
040 * <p>Note that different measures (in the same Star) occupy the same
041 * Aggregation.  Aggregations belong to the AggregationManager, a singleton.</p>
042 *
043 * <p>Segments are pinned during the evaluation of a single MDX query. The query
044 * evaluates the expressions twice. The first pass, it finds which cell values
045 * it needs, pins the segments containing the ones which are already present
046 * (one pin-count for each cell value used), and builds a {@link CellRequest
047 * cell request} for those which are not present. It executes the cell request
048 * to bring the required cell values into the cache, again, pinned. Then it
049 * evalutes the query a second time, knowing that all cell values are
050 * available. Finally, it releases the pins.</p>
051 *
052 * <p>A Segment may have a list of {@link ExcludedRegion} objects. These are
053 * caused by cache flushing. Usually a segment is a hypercube: it is defined by
054 * a set of values on each of its axes. But after a cache flush request, a
055 * segment may have a rectangular 'hole', and therefore not be a hypercube
056 * anymore.
057 *
058 * <p>For example, the segment defined by {CA, OR, WA} * {F, M} is a
059 * 2-dimensional hyper-rectangle with 6 cells. After flushing {CA, OR, TX} *
060 * {F}, the result is 4 cells:
061 *
062 * <pre>
063 *     F     M
064 * CA  out   in
065 * OR  out   in
066 * WA  in    in
067 * </pre>
068 *
069 * defined by the original segment minus the region ({CA, OR} * {F}).
070 *
071 * @author jhyde
072 * @since 21 March, 2002
073 */
074public class Segment {
075    private static int nextId = 0; // generator for "id"
076
077    final int id; // for debug
078    private String desc;
079
080    /**
081     * This is set in the load method and is used during
082     * the processing of a particular aggregate load.
083     */
084    protected final RolapStar.Column[] columns;
085
086    public final RolapStar.Measure measure;
087
088    /**
089     * An array of axes, one for each constraining column, containing the values
090     * returned for that constraining column.
091     */
092    public final StarColumnPredicate[] predicates;
093
094    protected final RolapStar star;
095    protected final BitKey constrainedColumnsBitKey;
096
097    /**
098     * List of regions to ignore when reading this segment. This list is
099     * populated when a region is flushed. The cells for these regions may be
100     * physically in the segment, because trimming segments can be expensive,
101     * but should still be ignored.
102     */
103    protected final List<ExcludedRegion> excludedRegions;
104
105    private final int aggregationKeyHashCode;
106    protected final List<StarPredicate> compoundPredicateList;
107
108    private final SegmentHeader segmentHeader;
109
110    private static final Logger LOGGER = Logger.getLogger(Segment.class);
111
112    /**
113     * Creates a <code>Segment</code>; it's not loaded yet.
114     *
115     * @param star Star that this Segment belongs to
116     * @param measure Measure whose values this Segment contains
117     * @param predicates List of predicates constraining each axis
118     * @param excludedRegions List of regions which are not in this segment.
119     */
120    public Segment(
121        RolapStar star,
122        BitKey constrainedColumnsBitKey,
123        RolapStar.Column[] columns,
124        RolapStar.Measure measure,
125        StarColumnPredicate[] predicates,
126        List<ExcludedRegion> excludedRegions,
127        final List<StarPredicate> compoundPredicateList)
128    {
129        this.id = nextId++;
130        this.star = star;
131        this.constrainedColumnsBitKey = constrainedColumnsBitKey;
132        this.columns = columns;
133        this.measure = measure;
134        this.predicates = predicates;
135        this.excludedRegions = excludedRegions;
136        this.compoundPredicateList = compoundPredicateList;
137        final List<BitKey> compoundPredicateBitKeys =
138            compoundPredicateList == null
139                ? null
140                : new AbstractList<BitKey>() {
141                    public BitKey get(int index) {
142                        return compoundPredicateList.get(index)
143                            .getConstrainedColumnBitKey();
144                    }
145
146                    public int size() {
147                        return compoundPredicateList.size();
148                    }
149                };
150        this.aggregationKeyHashCode =
151            AggregationKey.computeHashCode(
152                constrainedColumnsBitKey,
153                star,
154                compoundPredicateBitKeys);
155        this.segmentHeader = SegmentBuilder.toHeader(this);
156    }
157
158    /**
159     * Returns the constrained columns.
160     */
161    public RolapStar.Column[] getColumns() {
162        return columns;
163    }
164
165    /**
166     * Returns the star.
167     */
168    public RolapStar getStar() {
169        return star;
170    }
171
172    /**
173     * Returns the list of compound predicates.
174     */
175    public List<StarPredicate> getCompoundPredicateList() {
176        return compoundPredicateList;
177    }
178
179    /**
180     * Returns the BitKey for ALL columns (Measures and Levels) involved in the
181     * query.
182     */
183    public BitKey getConstrainedColumnsBitKey() {
184        return constrainedColumnsBitKey;
185    }
186
187    private void describe(StringBuilder buf, boolean values) {
188        final String sep = Util.nl + "    ";
189        buf.append(printSegmentHeaderInfo(sep));
190
191        for (int i = 0; i < columns.length; i++) {
192            buf.append(sep);
193            buf.append(columns[i].getExpression().getGenericExpression());
194            describeAxes(buf, i, values);
195        }
196        if (!excludedRegions.isEmpty()) {
197            buf.append(sep);
198            buf.append("excluded={");
199            int k = 0;
200            for (ExcludedRegion excludedRegion : excludedRegions) {
201                if (k++ > 0) {
202                    buf.append(", ");
203                }
204                excludedRegion.describe(buf);
205            }
206            buf.append('}');
207        }
208        buf.append('}');
209    }
210
211    protected void describeAxes(StringBuilder buf, int i, boolean values) {
212        predicates[i].describe(buf);
213    }
214
215    private String printSegmentHeaderInfo(String sep) {
216        StringBuilder buf = new StringBuilder();
217        buf.append("Segment #");
218        buf.append(id);
219        buf.append(" {");
220        buf.append(sep);
221        buf.append("measure=");
222        buf.append(
223            measure.getExpression() == null
224                ? measure.getAggregator().getExpression("*")
225                : measure.getAggregator().getExpression(
226                    measure.getExpression().getGenericExpression()));
227        return buf.toString();
228    }
229
230    public String toString() {
231        if (this.desc == null) {
232            StringBuilder buf = new StringBuilder(64);
233            describe(buf, false);
234            this.desc = buf.toString();
235        }
236        return this.desc;
237    }
238
239    /**
240     * Returns whether a cell value is excluded from this segment.
241     */
242    protected final boolean isExcluded(Object[] keys) {
243        // Performance critical: cannot use foreach
244        final int n = excludedRegions.size();
245        //noinspection ForLoopReplaceableByForEach
246        for (int i = 0; i < n; i++) {
247            ExcludedRegion excludedRegion = excludedRegions.get(i);
248            if (excludedRegion.wouldContain(keys)) {
249                return true;
250            }
251        }
252        return false;
253    }
254
255    /**
256     * Prints the state of this <code>Segment</code>, including constraints
257     * and values. Blocks the current thread until the segment is loaded.
258     *
259     * @param pw Writer
260     */
261    public void print(PrintWriter pw) {
262        final StringBuilder buf = new StringBuilder();
263        describe(buf, true);
264        pw.print(buf.toString());
265        pw.println();
266    }
267
268    public List<ExcludedRegion> getExcludedRegions() {
269        return excludedRegions;
270    }
271
272    SegmentDataset createDataset(
273        SegmentAxis[] axes,
274        boolean sparse,
275        SqlStatement.Type type,
276        int size)
277    {
278        if (sparse) {
279            return new SparseSegmentDataset();
280        } else {
281            switch (type) {
282            case OBJECT:
283            case LONG:
284            case STRING:
285                return new DenseObjectSegmentDataset(axes, size);
286            case INT:
287                return new DenseIntSegmentDataset(axes, size);
288            case DOUBLE:
289                return new DenseDoubleSegmentDataset(axes, size);
290            default:
291                throw Util.unexpected(type);
292            }
293        }
294    }
295
296    public boolean matches(
297        AggregationKey aggregationKey,
298        RolapStar.Measure measure)
299    {
300        // Perform high-selectivity comparisons first.
301        return aggregationKeyHashCode == aggregationKey.hashCode()
302            && this.measure == measure
303            && matchesInternal(aggregationKey);
304    }
305
306    private boolean matchesInternal(AggregationKey aggKey) {
307        return
308            constrainedColumnsBitKey.equals(
309                aggKey.getConstrainedColumnsBitKey())
310            && star.equals(aggKey.getStar())
311            && AggregationKey.equal(
312                compoundPredicateList,
313                aggKey.compoundPredicateList);
314    }
315
316    /**
317     * Definition of a region of values which are not in a segment.
318     */
319    public static interface ExcludedRegion {
320        /**
321         * Tells whether this exclusion region would contain
322         * the cell corresponding to the keys.
323         */
324        public boolean wouldContain(Object[] keys);
325
326        /**
327         * Returns the arity of this region.
328         */
329        public int getArity();
330
331        /**
332         * Describes this exclusion region in a human readable way.
333         */
334        public void describe(StringBuilder buf);
335
336        /**
337         * Returns an approximation of the number of cells exceluded
338         * in this region.
339         */
340        public int getCellCount();
341    }
342
343    public SegmentHeader getHeader() {
344        return this.segmentHeader;
345    }
346}
347
348// End Segment.java