001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/agg/CellRequest.java#26 $
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) 2002-2002 Kana Software, Inc.
007    // Copyright (C) 2002-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, 21 March, 2002
012    */
013    
014    package mondrian.rolap.agg;
015    
016    import mondrian.rolap.*;
017    
018    import java.util.*;
019    
020    /**
021     * A <code>CellRequest</code> contains the context necessary to get a cell
022     * value from a star.
023     *
024     * @author jhyde
025     * @since 21 March, 2002
026     * @version $Id: //open/mondrian/src/main/mondrian/rolap/agg/CellRequest.java#26 $
027     */
028    public class CellRequest {
029        private final RolapStar.Measure measure;
030        public final boolean extendedContext;
031        public final boolean drillThrough;
032        /**
033         * List of {@link mondrian.rolap.RolapStar.Column}s being which have values
034         * in this request.
035         */
036        private final List<RolapStar.Column> constrainedColumnList =
037            new ArrayList<RolapStar.Column>();
038    
039        private final List<StarColumnPredicate> columnPredicateList =
040            new ArrayList<StarColumnPredicate>();
041    
042        /*
043         * Array of column values;
044         * Not used to represent the compound members along one or more dimensions.
045         */
046        private Object[] singleValues;
047    
048        /**
049         * After all of the columns are loaded into the constrainedColumnList instance
050         * variable, this columnsCache is created the first time the getColumns
051         * method is called.
052         * <p>
053         * It is assumed that the call to all additional columns,
054         * {@link #addConstrainedColumn}, will not be called after the first call to
055         * the {@link #getConstrainedColumns()} method.
056         */
057        private RolapStar.Column[] columnsCache = null;
058    
059        /**
060         * A bit is set for each column in the column list. Allows us to rapidly
061         * figure out whether two requests are for the same column set.
062         * These are all of the columns that are involved with a query, that is, all
063         * required to be present in an aggregate table for the table be used to
064         * fulfill the query.
065         */
066        private final BitKey constrainedColumnsBitKey;
067    
068        /**
069         * Map from BitKey (representing a group of columns that forms a
070         * compound key) to StarPredicate (representing the predicate
071         * defining the compound member).
072         *
073         * <p>We use LinkedHashMap so that the entries occur in deterministic
074         * order; otherwise, successive runs generate different SQL queries.
075         * Another solution worth considering would be to use the inherent ordering
076         * of BitKeys and create a sorted map.
077         */
078        private final Map<BitKey, StarPredicate> compoundPredicateMap =
079            new LinkedHashMap<BitKey, StarPredicate>();
080    
081        /**
082         * Whether the request is impossible to satisfy. This is set to 'true' if
083         * contradictory constraints are applied to the same column. For example,
084         * the levels [Customer].[City] and [Cities].[City] map to the same column
085         * via the same join-path, and one constraint sets city = 'Burbank' and
086         * another sets city = 'Los Angeles'.
087         */
088        private boolean unsatisfiable;
089    
090        /**
091         * The columnPredicateList and columnsCache must be set after all constraints
092         * have been added. This is used by access methods to determine if
093         * both columnPredicateList and columnsCache need to be generated.
094         */
095        private boolean isDirty = true;
096    
097        /**
098         * Creates a {@link CellRequest}.
099         *
100         * @param measure Measure the request is for
101         * @param extendedContext If a drill-through request, whether to join in
102         *   unconstrained levels so as to display extra columns
103         * @param drillThrough Whether this is a request for a drill-through set
104         */
105        public CellRequest(
106            RolapStar.Measure measure,
107            boolean extendedContext,
108            boolean drillThrough)
109        {
110            this.measure = measure;
111            this.extendedContext = extendedContext;
112            this.drillThrough = drillThrough;
113            this.constrainedColumnsBitKey =
114                BitKey.Factory.makeBitKey(measure.getStar().getColumnCount());
115        }
116    
117        /**
118         * Adds a constraint to this request.
119         *
120         * @param column Column to constraint
121         * @param predicate Constraint to apply, or null to add column to the
122         *   output without applying constraint
123         */
124        public final void addConstrainedColumn(
125            RolapStar.Column column,
126            StarColumnPredicate predicate)
127        {
128            assert columnsCache == null;
129    
130            final int bitPosition = column.getBitPosition();
131            if (this.constrainedColumnsBitKey.get(bitPosition)) {
132                // This column is already constrained. Unless the value is the same,
133                // or this value or the previous value is null (meaning
134                // unconstrained) the request will never return any results.
135                int index = constrainedColumnList.indexOf(column);
136                assert index >= 0;
137                final StarColumnPredicate prevValue = columnPredicateList.get(index);
138                if (prevValue == null) {
139                    // Previous column was unconstrained. Constrain on new
140                    // value.
141                } else if (predicate == null) {
142                    // Previous column was constrained. Nothing to do.
143                    return;
144                } else if (predicate.equalConstraint(prevValue)) {
145                            // Same constraint again. Nothing to do.
146                            return;
147                } else {
148                    // Different constraint. Request is impossible to satisfy.
149                    predicate = null;
150                    unsatisfiable = true;
151                }
152                columnPredicateList.set(index, predicate);
153    
154            } else {
155                this.constrainedColumnList.add(column);
156                this.constrainedColumnsBitKey.set(bitPosition);
157                this.columnPredicateList.add(predicate);
158            }
159        }
160    
161        /**
162         * Add compound member (formed via aggregate function) constraint to the
163         * Cell.
164         *
165         * @param compoundBitKey
166         * @param compoundPredicate
167         */
168        public void addAggregateList(
169            BitKey compoundBitKey,
170            StarPredicate compoundPredicate)
171        {
172            compoundPredicateMap.put(compoundBitKey, compoundPredicate);
173        }
174    
175        public RolapStar.Measure getMeasure() {
176            return measure;
177        }
178    
179        public RolapStar.Column[] getConstrainedColumns() {
180            if (this.columnsCache == null) {
181                // This is called more than once so caching the value makes
182                // sense.
183                check();
184            }
185            return this.columnsCache;
186        }
187    
188        /**
189         * Returns the BitKey for the list of columns.
190         *
191         * @return BitKey for the list of columns
192         */
193        public BitKey getConstrainedColumnsBitKey() {
194            return constrainedColumnsBitKey;
195        }
196    
197        /**
198         * Get the map of compound predicates
199         * @return predicate map
200         */
201        public Map<BitKey, StarPredicate> getCompoundPredicateMap() {
202            return compoundPredicateMap;
203        }
204    
205        /**
206         * Builds the {@link #columnsCache} and reorders the
207         * {@link #columnPredicateList}
208         * based upon bit key position of the columns.
209         */
210        private void check() {
211            if (isDirty) {
212                final int size = constrainedColumnList.size();
213                this.columnsCache = new RolapStar.Column[size];
214                final StarColumnPredicate[] oldColumnPredicates =
215                    columnPredicateList.toArray(
216                        new StarColumnPredicate[columnPredicateList.size()]);
217                columnPredicateList.clear();
218                int i = 0;
219                for (int bitPos : constrainedColumnsBitKey) {
220                    // NOTE: If the RolapStar.Column were stored in maybe a Map
221                    // rather than the constrainedColumnList List, we would
222                    // not have to for-loop over the list for each bit position.
223                    for (int j = 0; j < size; j++) {
224                        RolapStar.Column rc = constrainedColumnList.get(j);
225                        if (rc.getBitPosition() == bitPos) {
226                            int index = constrainedColumnList.indexOf(rc);
227                            final StarColumnPredicate value =
228                                oldColumnPredicates[index];
229                            columnPredicateList.add(value);
230                            columnsCache[i++] = rc;
231                            break;
232                        }
233                    }
234                }
235                isDirty = false;
236            }
237        }
238    
239        public List<StarColumnPredicate> getValueList() {
240            check();
241            return columnPredicateList;
242        }
243    
244        /**
245         * Returns an array of the values for each column.
246         *
247         * <p>The caller must check whether this request is satisfiable before
248         * calling this method. May throw {@link NullPointerException} if request
249         * is not satisfiable.
250         *
251         * @pre !isUnsatisfiable()
252         * @return Array of values for each column
253         */
254        public Object[] getSingleValues() {
255            assert !unsatisfiable;
256            if (singleValues == null) {
257                check();
258                singleValues = new Object[columnPredicateList.size()];
259                final int size = columnPredicateList.size();
260                for (int i = 0; i < size; i++) {
261                    ValueColumnPredicate predicate =
262                        (ValueColumnPredicate) columnPredicateList.get(i);
263                    singleValues[i] = predicate.getValue();
264                }
265            }
266            return singleValues;
267        }
268    
269        /**
270         * Returns whether this cell request is impossible to satisfy.
271         * This occurs when the same column has two or more inconsistent
272         * constraints.
273         *
274         * @return whether this cell request is impossible to satisfy
275         */
276        public boolean isUnsatisfiable() {
277            return unsatisfiable;
278        }
279    }
280    
281    // End CellRequest.java