/*
// This software is subject to the terms of the Eclipse Public License v1.0
// Agreement, available at the following URL:
// http://www.eclipse.org/legal/epl-v10.html.
// You must accept the terms of that agreement to use this software.
//
// Copyright (c) 2002-2014 Pentaho Corporation.. All rights reserved.
*/
package mondrian.rolap.agg;
import mondrian.olap.Aggregator;
import mondrian.olap.Util;
import mondrian.rolap.*;
import mondrian.rolap.agg.Segment.ExcludedRegion;
import mondrian.rolap.sql.SqlQuery;
import mondrian.spi.*;
import mondrian.spi.Dialect.Datatype;
import mondrian.util.ArraySortedSet;
import mondrian.util.Pair;
import org.apache.log4j.Logger;
import org.olap4j.impl.UnmodifiableArrayList;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
/**
* Helper class that contains methods to convert between
* {@link Segment} and {@link SegmentHeader}, and also
* {@link SegmentWithData} and {@link SegmentBody}.
*
* @author LBoudreau
*/
public class SegmentBuilder {
private static final Logger LOGGER =
Logger.getLogger(SegmentBuilder.class);
/**
* Converts a segment plus a {@link SegmentBody} into a
* {@link mondrian.rolap.agg.SegmentWithData}.
*
* @param segment Segment
* @param sb Segment body
* @return SegmentWithData
*/
public static SegmentWithData addData(Segment segment, SegmentBody sb) {
// Load the axis keys for this segment
SegmentAxis[] axes =
new SegmentAxis[segment.predicates.length];
for (int i = 0; i < segment.predicates.length; i++) {
StarColumnPredicate predicate =
segment.predicates[i];
axes[i] =
new SegmentAxis(
predicate,
sb.getAxisValueSets()[i],
sb.getNullAxisFlags()[i]);
}
final SegmentDataset dataSet = createDataset(sb, axes);
return new SegmentWithData(segment, dataSet, axes);
}
/**
* Creates a SegmentDataset that contains the cached
* data and is initialized to be used with the supplied segment.
*
* @param body Segment with which the returned dataset will be associated
* @param axes Segment axes, containing actual column values
* @return A SegmentDataset object that contains cached data.
*/
private static SegmentDataset createDataset(
SegmentBody body,
SegmentAxis[] axes)
{
final SegmentDataset dataSet;
if (body instanceof DenseDoubleSegmentBody) {
dataSet =
new DenseDoubleSegmentDataset(
axes,
(double[]) body.getValueArray(),
body.getNullValueIndicators());
} else if (body instanceof DenseIntSegmentBody) {
dataSet =
new DenseIntSegmentDataset(
axes,
(int[]) body.getValueArray(),
body.getNullValueIndicators());
} else if (body instanceof DenseObjectSegmentBody) {
dataSet =
new DenseObjectSegmentDataset(
axes, (Object[]) body.getValueArray());
} else if (body instanceof SparseSegmentBody) {
dataSet = new SparseSegmentDataset(body.getValueMap());
} else {
throw Util.newInternal(
"Unknown segment body type: " + body.getClass() + ": " + body);
}
return dataSet;
}
/**
* Creates a segment from a SegmentHeader. The star,
* constrainedColsBitKey, constrainedColumns and measure arguments are a
* helping hand, because we know what we were looking for.
*
* @param header The header to convert.
* @param star Star
* @param constrainedColumnsBitKey Constrained columns
* @param constrainedColumns Constrained columns
* @param measure Measure
* @return Segment
*/
public static Segment toSegment(
SegmentHeader header,
RolapStar star,
BitKey constrainedColumnsBitKey,
RolapStar.Column[] constrainedColumns,
RolapStar.Measure measure,
List<StarPredicate> compoundPredicates)
{
final List<StarColumnPredicate> predicateList =
new ArrayList<StarColumnPredicate>();
for (int i = 0; i < constrainedColumns.length; i++) {
RolapStar.Column constrainedColumn = constrainedColumns[i];
final SortedSet<Comparable> values =
header.getConstrainedColumns().get(i).values;
StarColumnPredicate predicate;
if (values == null) {
predicate =
new LiteralStarPredicate(
constrainedColumn,
true);
} else if (values.size() == 1) {
predicate =
new ValueColumnPredicate(
constrainedColumn,
values.first());
} else {
final List<StarColumnPredicate> valuePredicateList =
new ArrayList<StarColumnPredicate>();
for (Object value : values) {
valuePredicateList.add(
new ValueColumnPredicate(
constrainedColumn,
value));
}
predicate =
new ListColumnPredicate(
constrainedColumn,
valuePredicateList);
}
predicateList.add(predicate);
}
return new Segment(
star,
constrainedColumnsBitKey,
constrainedColumns,
measure,
predicateList.toArray(
new StarColumnPredicate[predicateList.size()]),
new ExcludedRegionList(header),
compoundPredicates);
}
/**
* Given a collection of segments, all of the same dimensionality, rolls up
* to create a segment with reduced dimensionality.
*
* @param map Source segment headers and bodies
* @param keepColumns A list of column names to keep as part of
* the rolled up segment.
* @param targetBitkey The column bit key to match with the
* resulting segment.
* @param rollupAggregator The aggregator to use to rollup.
* @return Segment header and body of requested dimensionality
* @param datatype The data type to use.
*/
public static Pair<SegmentHeader, SegmentBody> rollup(
Map<SegmentHeader, SegmentBody> map,
Set<String> keepColumns,
BitKey targetBitkey,
Aggregator rollupAggregator,
Datatype datatype)
{
class AxisInfo {
SegmentColumn column;
SortedSet<Comparable> requestedValues;
SortedSet<Comparable> valueSet;
Comparable[] values;
boolean hasNull;
int src;
boolean lostPredicate;
}
assert allHeadersHaveSameDimensionality(map.keySet());
// store the map values in a list to assure the first header
// loaded here is consistent w/ the first segment processed below.
List<Map.Entry<SegmentHeader, SegmentBody>> segments =
UnmodifiableArrayList.of(map.entrySet());
final SegmentHeader firstHeader = segments.get(0).getKey();
final AxisInfo[] axes =
new AxisInfo[keepColumns.size()];
int z = 0, j = 0;
for (SegmentColumn column : firstHeader.getConstrainedColumns()) {
if (keepColumns.contains(column.columnExpression)) {
final AxisInfo axisInfo = axes[z++] = new AxisInfo();
axisInfo.src = j;
axisInfo.column = column;
axisInfo.requestedValues = column.values;
}
j++;
}
// Compute the sets of values in each axis of the target segment. These
// are the intersection of the input axes.
for (Map.Entry<SegmentHeader, SegmentBody> entry : segments) {
final SegmentHeader header = entry.getKey();
for (AxisInfo axis : axes) {
final SortedSet<Comparable> values =
entry.getValue().getAxisValueSets()[axis.src];
final SegmentColumn headerColumn =
header.getConstrainedColumn(axis.column.columnExpression);
final boolean hasNull =
entry.getValue().getNullAxisFlags()[axis.src];
final SortedSet<Comparable> requestedValues =
headerColumn.getValues();
if (axis.valueSet == null) {
axis.valueSet = new TreeSet<Comparable>(values);
axis.hasNull = hasNull;
axis.requestedValues = requestedValues;
} else {
final SortedSet<Comparable> filteredValues;
final boolean filteredHasNull;
if (axis.requestedValues == null
&& requestedValues == null)
{
// there are 2+ segments that are unconstrained for
// this axis. While unconstrained, individually
// they may not have all values present.
// Make sure we don't lose any values.
filteredValues = axis.valueSet;
filteredValues.addAll(new TreeSet<Comparable>(values));
filteredHasNull = hasNull || axis.hasNull;
} else if (axis.requestedValues == null) {
filteredValues = values;
filteredHasNull = hasNull;
axis.column = headerColumn;
} else if (requestedValues == null) {
// this axis is wildcarded
filteredValues = axis.requestedValues;
filteredHasNull = axis.hasNull;
} else {
filteredValues = Util.intersect(
requestedValues,
axis.requestedValues);
// SegmentColumn predicates cannot ask for the null
// value (at present).
filteredHasNull = false;
}
axis.valueSet = filteredValues;
axis.hasNull = axis.hasNull || filteredHasNull;
if (!Util.equals(axis.requestedValues, requestedValues)) {
if (axis.requestedValues == null) {
// Downgrade from wildcard to a specific list.
axis.requestedValues = requestedValues;
} else if (requestedValues != null) {
// Segment requests have incompatible predicates.
// Best we can say is "we must have asked for the
// values that came back".
axis.lostPredicate = true;
}
}
}
}
}
for (AxisInfo axis : axes) {
axis.values =
axis.valueSet.toArray(new Comparable[axis.valueSet.size()]);
}
// Populate cells.
//
// (This is a rough implementation, very inefficient. It makes all
// segment types pretend to be sparse, for purposes of reading. It
// maps all axis ordinals to a value, then back to an axis ordinal,
// even if this translation were not necessary, say if the source and
// target axes had the same set of values. And it always creates a
// sparse segment.
//
// We should do really efficient rollup if the source is an array: we
// should box values (e.g double to Double and back), and we should read
// a stripe of values from the and add them up into a single cell.
final Map<CellKey, List<Object>> cellValues =
new HashMap<CellKey, List<Object>>();
List<List<Comparable>> addedIntersections =
new ArrayList<List<Comparable>>();
for (Map.Entry<SegmentHeader, SegmentBody> entry : map.entrySet()) {
final int[] pos = new int[axes.length];
final Comparable[][] valueArrays =
new Comparable[firstHeader.getConstrainedColumns().size()][];
final SegmentBody body = entry.getValue();
// Copy source value sets into arrays. For axes that are being
// projected away, store null.
z = 0;
for (SortedSet<Comparable> set : body.getAxisValueSets()) {
valueArrays[z] = keepColumns.contains(
firstHeader.getConstrainedColumns().get(z).columnExpression)
? set.toArray(new Comparable[set.size()])
: null;
++z;
}
Map<CellKey, Object> v = body.getValueMap();
entryLoop:
for (Map.Entry<CellKey, Object> vEntry : v.entrySet()) {
z = 0;
for (int i = 0; i < vEntry.getKey().size(); i++) {
final Comparable[] valueArray = valueArrays[i];
if (valueArray == null) {
continue;
}
final int ordinal = vEntry.getKey().getOrdinals()[i];
final int targetOrdinal;
if (axes[z].hasNull && ordinal == valueArray.length) {
targetOrdinal = axes[z].valueSet.size();
} else {
final Comparable value = valueArray[ordinal];
if (value == null) {
targetOrdinal = axes[z].valueSet.size();
} else {
targetOrdinal =
Util.binarySearch(
axes[z].values,
0, axes[z].values.length,
value);
}
}
if (targetOrdinal >= 0) {
pos[z++] = targetOrdinal;
} else {
// This happens when one of the rollup candidate doesn't
// contain the requested cell.
continue entryLoop;
}
}
final CellKey ck = CellKey.Generator.newCellKey(pos);
if (!cellValues.containsKey(ck)) {
cellValues.put(ck, new ArrayList<Object>());
}
List<Comparable> colValues = getColumnValsAtCellKey(
body, vEntry.getKey());
if (!addedIntersections.contains(colValues)) {
// only add the cell value if we haven't already.
// there is a potential double add if segments overlap
cellValues.get(ck).add(vEntry.getValue());
addedIntersections.add(colValues);
}
}
}
// Build the axis list.
final List<Pair<SortedSet<Comparable>, Boolean>> axisList =
new ArrayList<Pair<SortedSet<Comparable>, Boolean>>();
BigInteger bigValueCount = BigInteger.ONE;
for (AxisInfo axis : axes) {
axisList.add(Pair.of(axis.valueSet, axis.hasNull));
int size = axis.values.length;
bigValueCount = bigValueCount.multiply(
BigInteger.valueOf(axis.hasNull ? size + 1 : size));
}
// The logic used here for the sparse check follows
// SegmentLoader.setAxisDataAndDecideSparseUse.
// The two methods use different data structures (AxisInfo/SegmentAxis)
// so combining logic is probably more trouble than it's worth.
final boolean sparse =
bigValueCount.compareTo
(BigInteger.valueOf(Integer.MAX_VALUE)) > 0
|| SegmentLoader.useSparse(
bigValueCount.doubleValue(),
cellValues.size());
final int[] axisMultipliers =
computeAxisMultipliers(axisList);
final SegmentBody body;
// Peak at the values and determine the best way to store them
// (whether to use a dense native dataset or a sparse one.
if (cellValues.size() == 0) {
// Just store the data into an empty dense object dataset.
body =
new DenseObjectSegmentBody(
new Object[0],
axisList);
} else if (sparse) {
// The rule says we must use a sparse dataset.
// First, aggregate the values of each key.
final Map<CellKey, Object> data =
new HashMap<CellKey, Object>();
for (Entry<CellKey, List<Object>> entry
: cellValues.entrySet())
{
data.put(
CellKey.Generator.newCellKey(entry.getKey().getOrdinals()),
rollupAggregator.aggregate(
entry.getValue(),
datatype));
}
body =
new SparseSegmentBody(
data,
axisList);
} else {
final BitSet nullValues;
final int valueCount = bigValueCount.intValue();
switch (datatype) {
case Integer:
final int[] ints = new int[valueCount];
nullValues = Util.bitSetBetween(0, valueCount);
for (Entry<CellKey, List<Object>> entry
: cellValues.entrySet())
{
final int offset =
CellKey.Generator.getOffset(
entry.getKey().getOrdinals(), axisMultipliers);
final Object value =
rollupAggregator.aggregate(
entry.getValue(),
datatype);
if (value != null) {
ints[offset] = (Integer) value;
nullValues.clear(offset);
}
}
body =
new DenseIntSegmentBody(
nullValues,
ints,
axisList);
break;
case Numeric:
final double[] doubles = new double[valueCount];
nullValues = Util.bitSetBetween(0, valueCount);
for (Entry<CellKey, List<Object>> entry
: cellValues.entrySet())
{
final int offset =
CellKey.Generator.getOffset(
entry.getKey().getOrdinals(), axisMultipliers);
final Object value =
rollupAggregator.aggregate(
entry.getValue(),
datatype);
if (value != null) {
doubles[offset] = (Double) value;
nullValues.clear(offset);
}
}
body =
new DenseDoubleSegmentBody(
nullValues,
doubles,
axisList);
break;
default:
final Object[] objects = new Object[valueCount];
for (Entry<CellKey, List<Object>> entry
: cellValues.entrySet())
{
final int offset =
CellKey.Generator.getOffset(
entry.getKey().getOrdinals(), axisMultipliers);
objects[offset] =
rollupAggregator.aggregate(
entry.getValue(),
datatype);
}
body =
new DenseObjectSegmentBody(
objects,
axisList);
}
}
// Create header.
final List<SegmentColumn> constrainedColumns =
new ArrayList<SegmentColumn>();
for (int i = 0; i < axes.length; i++) {
AxisInfo axisInfo = axes[i];
constrainedColumns.add(
new SegmentColumn(
axisInfo.column.getColumnExpression(),
axisInfo.column.getValueCount(),
axisInfo.lostPredicate
? axisList.get(i).left
: axisInfo.column.values));
}
final SegmentHeader header =
new SegmentHeader(
firstHeader.schemaName,
firstHeader.schemaChecksum,
firstHeader.cubeName,
firstHeader.measureName,
constrainedColumns,
firstHeader.compoundPredicates,
firstHeader.rolapStarFactTableName,
targetBitkey,
Collections.<SegmentColumn>emptyList());
if (LOGGER.isDebugEnabled()) {
StringBuilder builder = new StringBuilder();
builder.append("Rolling up segments with parameters: \n");
builder.append("keepColumns=" + keepColumns + "\n");
builder.append("aggregator=" + rollupAggregator + "\n");
builder.append("datatype=" + datatype + "\n");
for (Map.Entry<SegmentHeader, SegmentBody > segment : segments) {
builder.append(segment.getKey() + "\n");
}
builder.append("AxisInfos constructed:");
for (AxisInfo axis : axes) {
SortedSet<Comparable> colVals = axis.column.getValues();
builder.append(
String.format(
"column.columnExpression=%s\n"
+ "column.valueCount=%s\n"
+ "column.values=%s\n"
+ "requestedValues=%s\n"
+ "valueSet=%s\n"
+ "values=%s\n"
+ "hasNull=%b\n"
+ "src=%d\n"
+ "lostPredicate=%b\n",
axis.column.columnExpression,
axis.column.getValueCount(),
Arrays.toString(
colVals == null ? null
: colVals.toArray()),
axis.requestedValues,
axis.valueSet,
Arrays.asList(axis.values),
axis.hasNull,
axis.src,
axis.lostPredicate));
}
builder.append("Resulted in Segment: \n");
builder.append(header);
builder.append(body.toString());
LOGGER.debug(builder.toString());
}
return Pair.of(header, body);
}
private static List<Comparable> getColumnValsAtCellKey(
SegmentBody body, CellKey cellKey)
{
List<Comparable> columnValues = new ArrayList<Comparable>();
for (int i = 0; i < body.getAxisValueSets().length; i++) {
List<Comparable> columnVals = new ArrayList<Comparable>();
columnVals.addAll(body.getAxisValueSets()[i]);
int valCoord = body.getNullAxisFlags()[i]
? cellKey.getAxis(i) - 1
: cellKey.getAxis(i);
if (valCoord >= 0) {
columnValues.add(columnVals.get(valCoord));
} else {
columnValues.add(null);
}
}
return columnValues;
}
private static boolean allHeadersHaveSameDimensionality(
Set<SegmentHeader> headers)
{
final Iterator<SegmentHeader> headerIter = headers.iterator();
final SegmentHeader firstHeader = headerIter.next();
BitKey bitKey = firstHeader.getConstrainedColumnsBitKey();
while (headerIter.hasNext()) {
final SegmentHeader nextHeader = headerIter.next();
if (!bitKey.equals(nextHeader.getConstrainedColumnsBitKey())) {
return false;
}
}
return true;
}
private static int[] computeAxisMultipliers(
List<Pair<SortedSet<Comparable>, Boolean>> axes)
{
final int[] axisMultipliers = new int[axes.size()];
int multiplier = 1;
for (int i = axes.size() - 1; i >= 0; --i) {
axisMultipliers[i] = multiplier;
// if the nullAxisFlag is set we need to offset by 1.
int nullAxisAdjustment = axes.get(i).right ? 1 : 0;
multiplier *= (axes.get(i).left.size() + nullAxisAdjustment);
}
return axisMultipliers;
}
private static class ExcludedRegionList
extends AbstractList<Segment.ExcludedRegion>
implements Segment.ExcludedRegion
{
private final int cellCount;
private final SegmentHeader header;
public ExcludedRegionList(SegmentHeader header) {
this.header = header;
int cellCount = 1;
for (SegmentColumn cc : header.getExcludedRegions()) {
// TODO find a way to approximate the cardinality
// of wildcard columns.
if (cc.values != null) {
cellCount *= cc.values.size();
}
}
this.cellCount = cellCount;
}
public void describe(StringBuilder buf) {
// TODO
}
public int getArity() {
return header.getConstrainedColumns().size();
}
public int getCellCount() {
return cellCount;
}
public boolean wouldContain(Object[] keys) {
assert keys.length == header.getConstrainedColumns().size();
for (int i = 0; i < keys.length; i++) {
final SegmentColumn excl =
header.getExcludedRegion(
header.getConstrainedColumns().get(i).columnExpression);
if (excl == null) {
continue;
}
if (excl.values.contains(keys[i])) {
return true;
}
}
return false;
}
public ExcludedRegion get(int index) {
return this;
}
public int size() {
return 1;
}
}
/**
* Tells if the passed segment is a subset of this segment
* and could be used for a rollup in cache operation.
* @param segment A segment which might be a subset of the
* current segment.
* @return True or false.
*/
public static boolean isSubset(
SegmentHeader header,
Segment segment)
{
if (!segment.getStar().getSchema().getName()
.equals(header.schemaName))
{
return false;
}
if (!segment.getStar().getFactTable().getAlias()
.equals(header.rolapStarFactTableName))
{
return false;
}
if (!segment.measure.getName().equals(header.measureName)) {
return false;
}
if (!segment.measure.getCubeName().equals(header.cubeName)) {
return false;
}
if (segment.getConstrainedColumnsBitKey()
.equals(header.constrainedColsBitKey))
{
return true;
}
return false;
}
public static List<SegmentColumn> toConstrainedColumns(
StarColumnPredicate[] predicates)
{
return toConstrainedColumns(
Arrays.asList(predicates));
}
public static List<SegmentColumn> toConstrainedColumns(
Collection<StarColumnPredicate> predicates)
{
List<SegmentColumn> ccs =
new ArrayList<SegmentColumn>();
for (StarColumnPredicate predicate : predicates) {
final List<Comparable> values =
new ArrayList<Comparable>();
predicate.values(Util.cast(values));
final Comparable[] valuesArray =
values.toArray(new Comparable[values.size()]);
if (valuesArray.length == 1 && valuesArray[0].equals(true)) {
ccs.add(
new SegmentColumn(
predicate.getConstrainedColumn()
.getExpression().getGenericExpression(),
predicate.getConstrainedColumn().getCardinality(),
null));
} else {
Arrays.sort(
valuesArray,
Util.SqlNullSafeComparator.instance);
ccs.add(
new SegmentColumn(
predicate.getConstrainedColumn()
.getExpression().getGenericExpression(),
predicate.getConstrainedColumn().getCardinality(),
new ArraySortedSet(valuesArray)));
}
}
return ccs;
}
/**
* Creates a SegmentHeader object describing the supplied
* Segment object.
*
* @param segment A segment object for which we want to generate
* a SegmentHeader.
* @return A SegmentHeader describing the supplied Segment object.
*/
public static SegmentHeader toHeader(Segment segment) {
final List<SegmentColumn> cc =
SegmentBuilder.toConstrainedColumns(segment.predicates);
final List<String> cp = new ArrayList<String>();
StringBuilder buf = new StringBuilder();
for (StarPredicate compoundPredicate : segment.compoundPredicateList) {
buf.setLength(0);
SqlQuery query =
new SqlQuery(
segment.star.getSqlQueryDialect());
compoundPredicate.toSql(query, buf);
cp.add(buf.toString());
}
final RolapSchema schema = segment.star.getSchema();
return new SegmentHeader(
schema.getName(),
schema.getChecksum(),
segment.measure.getCubeName(),
segment.measure.getName(),
cc,
cp,
segment.star.getFactTable().getAlias(),
segment.constrainedColumnsBitKey,
Collections.<SegmentColumn>emptyList());
}
private static RolapStar.Column[] getConstrainedColumns(
RolapStar star,
BitKey bitKey)
{
final List<RolapStar.Column> list =
new ArrayList<RolapStar.Column>();
for (int bit : bitKey) {
list.add(star.getColumn(bit));
}
return list.toArray(new RolapStar.Column[list.size()]);
}
/**
* Functor to convert a segment header and body into a
* {@link mondrian.rolap.agg.SegmentWithData}.
*/
public static interface SegmentConverter {
SegmentWithData convert(
SegmentHeader header,
SegmentBody body);
}
/**
* Implementation of {@link SegmentConverter} that uses an
* {@link mondrian.rolap.agg.AggregationKey}
* and {@link mondrian.rolap.agg.CellRequest} as context to
* convert a {@link mondrian.spi.SegmentHeader}.
*
* <p>This is nasty. A converter might be used for several headers, not
* necessarily with the context as the cell request and aggregation key.
* Converters only exist for fact tables and compound predicate combinations
* for which we have already done a load request.</p>
*
* <p>It would be much better if there was a way to convert compound
* predicates from strings to predicates. Then we could obsolete the
* messy context inside converters, and maybe obsolete converters
* altogether.</p>
*/
public static class SegmentConverterImpl implements SegmentConverter {
private final AggregationKey key;
private final CellRequest request;
public SegmentConverterImpl(AggregationKey key, CellRequest request) {
this.key = key;
this.request = request;
}
public SegmentWithData convert(
SegmentHeader header,
SegmentBody body)
{
final Segment segment =
toSegment(
header,
key.getStar(),
header.getConstrainedColumnsBitKey(),
getConstrainedColumns(
key.getStar(),
header.getConstrainedColumnsBitKey()),
request.getMeasure(),
key.getCompoundPredicateList());
return addData(segment, body);
}
}
/**
* Implementation of {@link SegmentConverter} that uses a star measure
* and a list of {@link StarPredicate}.
*/
public static class StarSegmentConverter implements SegmentConverter {
private final RolapStar.Measure measure;
private final List<StarPredicate> compoundPredicateList;
public StarSegmentConverter(
RolapStar.Measure measure,
List<StarPredicate> compoundPredicateList)
{
// The measure is wrapped in a weak reference because
// converters are put into the SegmentCacheIndex,
// but the registry of indexes is based as a weak
// list of the RolapStars.
// Simply put, the fact that converters have a hard
// link on the measure would prevents the GC from
// ever cleaning the registry. The circular references
// are a well known issue with weak lists.
// It is harmless to use a weak reference here because
// the measure is referenced by cubes and what-not,
// so it can't be GC'd before its time has come.
this.measure = measure;
this.compoundPredicateList = compoundPredicateList;
}
public SegmentWithData convert(
SegmentHeader header,
SegmentBody body)
{
final Segment segment =
toSegment(
header,
measure.getStar(),
header.getConstrainedColumnsBitKey(),
getConstrainedColumns(
measure.getStar(),
header.getConstrainedColumnsBitKey()),
measure,
compoundPredicateList);
return addData(segment, body);
}
}
}
// End SegmentBuilder.java