001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapCell.java#4 $
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) 2005-2007 Julian Hyde
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.mdx.*;
013 import mondrian.olap.*;
014 import mondrian.rolap.agg.AggregationManager;
015 import mondrian.rolap.agg.CellRequest;
016
017 import java.sql.*;
018 import java.util.List;
019 import java.util.ArrayList;
020
021 /**
022 * <code>RolapCell</code> implements {@link mondrian.olap.Cell} within a
023 * {@link RolapResult}.
024 *
025 * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapCell.java#4 $
026 */
027 class RolapCell implements Cell {
028 private final RolapResult result;
029 protected final int[] pos;
030 protected RolapResult.CellInfo ci;
031
032 RolapCell(RolapResult result, int[] pos, RolapResult.CellInfo ci) {
033 this.result = result;
034 this.pos = pos;
035 this.ci = ci;
036 }
037
038 public Object getValue() {
039 return ci.value;
040 }
041
042 public String getCachedFormatString() {
043 return ci.formatString;
044 }
045
046 public String getFormattedValue() {
047 return ci.getFormatValue();
048 }
049
050 public boolean isNull() {
051 return (ci.value == Util.nullValue);
052 }
053
054 public boolean isError() {
055 return (ci.value instanceof Throwable);
056 }
057
058 /**
059 * Create an sql query that, when executed, will return the drill through
060 * data for this cell. If the parameter extendedContext is true, then the
061 * query will include all the levels (i.e. columns) of non-constraining
062 * members (i.e. members which are at the "All" level).
063 * If the parameter extendedContext is false, the query will exclude
064 * the levels (coulmns) of non-constraining members.
065 */
066 public String getDrillThroughSQL(boolean extendedContext) {
067 RolapAggregationManager aggMan = AggregationManager.instance();
068 final Member[] currentMembers = getMembersForDrillThrough();
069 CellRequest cellRequest =
070 RolapAggregationManager.makeDrillThroughRequest(
071 currentMembers, extendedContext, result.getCube());
072 return (cellRequest == null)
073 ? null
074 : aggMan.getDrillThroughSql(cellRequest, false);
075 }
076
077
078 public int getDrillThroughCount() {
079 RolapAggregationManager aggMan = AggregationManager.instance();
080 final Member[] currentMembers = getMembersForDrillThrough();
081 CellRequest cellRequest =
082 RolapAggregationManager.makeDrillThroughRequest(
083 currentMembers, false, result.getCube());
084 if (cellRequest == null) {
085 return -1;
086 }
087 RolapConnection connection =
088 (RolapConnection) result.getQuery().getConnection();
089 final String sql = aggMan.getDrillThroughSql(cellRequest, true);
090 final SqlStatement stmt =
091 RolapUtil.executeQuery(
092 connection.getDataSource(),
093 sql,
094 "RolapCell.getDrillThroughCount",
095 "Error while counting drill-through");
096 try {
097 ResultSet rs = stmt.getResultSet();
098 rs.next();
099 ++stmt.rowCount;
100 return rs.getInt(1);
101 } catch (SQLException e) {
102 throw stmt.handle(e);
103 } finally {
104 stmt.close();
105 }
106 }
107
108 /**
109 * Returns whether it is possible to drill through this cell.
110 * Drill-through is possible if the measure is a stored measure
111 * and not possible for calculated measures.
112 *
113 * @return true if can drill through
114 */
115 public boolean canDrillThrough() {
116 // get current members
117 final Member[] currentMembers = getMembersForDrillThrough();
118 Cube x = chooseDrillThroughCube(currentMembers, result.getCube());
119 return x != null;
120 }
121
122 public static RolapCube chooseDrillThroughCube(
123 Member[] currentMembers,
124 RolapCube defaultCube)
125 {
126 if (defaultCube != null && defaultCube.isVirtual()) {
127 List<RolapCube> cubes = new ArrayList<RolapCube>();
128 for (RolapMember member : defaultCube.getMeasuresMembers()) {
129 if (member instanceof RolapVirtualCubeMeasure) {
130 RolapVirtualCubeMeasure measure =
131 (RolapVirtualCubeMeasure) member;
132 cubes.add(measure.getCube());
133 }
134 }
135 defaultCube = cubes.get(0);
136 assert !defaultCube.isVirtual();
137 }
138 final DrillThroughVisitor visitor =
139 new DrillThroughVisitor();
140 try {
141 for (Member member : currentMembers) {
142 visitor.handleMember(member);
143 }
144 } catch (RuntimeException e) {
145 if (e == DrillThroughVisitor.bomb) {
146 // No cubes left
147 return null;
148 } else {
149 throw e;
150 }
151 }
152 return visitor.cube == null
153 ? defaultCube
154 : visitor.cube;
155 }
156
157 private RolapEvaluator getEvaluator() {
158 return result.getCellEvaluator(pos);
159 }
160
161 private Member[] getMembersForDrillThrough() {
162 final Member[] currentMembers = result.getCellMembers(pos);
163
164 // replace member if we're dealing with a trivial formula
165 if (currentMembers[0] instanceof RolapHierarchy.RolapCalculatedMeasure) {
166 RolapHierarchy.RolapCalculatedMeasure measure =
167 (RolapHierarchy.RolapCalculatedMeasure)currentMembers[0];
168 if (measure.getFormula().getExpression() instanceof MemberExpr) {
169 currentMembers[0] =
170 ((MemberExpr)measure.getFormula().getExpression()).getMember();
171 }
172 }
173 return currentMembers;
174 }
175
176 public Object getPropertyValue(String propertyName) {
177 final boolean matchCase =
178 MondrianProperties.instance().CaseSensitive.get();
179 Property property = Property.lookup(propertyName, matchCase);
180 Object defaultValue = null;
181 if (property != null) {
182 switch (property.ordinal) {
183 case Property.CELL_ORDINAL_ORDINAL:
184 return result.getCellOrdinal(pos);
185 case Property.VALUE_ORDINAL:
186 return getValue();
187 case Property.FORMAT_STRING_ORDINAL:
188 if (ci.formatString == null) {
189 ci.formatString = getEvaluator().getFormatString();
190 }
191 return ci.formatString;
192 case Property.FORMATTED_VALUE_ORDINAL:
193 return getFormattedValue();
194 case Property.FONT_FLAGS_ORDINAL:
195 defaultValue = 0;
196 break;
197 case Property.SOLVE_ORDER_ORDINAL:
198 defaultValue = 0;
199 break;
200 default:
201 // fall through
202 }
203 }
204 return getEvaluator().getProperty(propertyName, defaultValue);
205 }
206
207 public Member getContextMember(Dimension dimension) {
208 return result.getMember(pos, dimension);
209 }
210
211 /**
212 * Visitor that walks over a cell's expression and checks whether the
213 * cell should allow drill-through. If not, throws the {@link #bomb}
214 * exception.
215 *
216 * <p>Examples:</p>
217 * <ul>
218 * <li>Literal 1 is drillable</li>
219 * <li>Member [Measures].[Unit Sales] is drillable</li>
220 * <li>Calculated member with expression [Measures].[Unit Sales] + 1 is drillable</li>
221 * <li>Calculated member with expression
222 * ([Measures].[Unit Sales], [Time].PrevMember) is not drillable</li>
223 * </ul>
224 */
225 private static class DrillThroughVisitor extends MdxVisitorImpl {
226 static final RuntimeException bomb = new RuntimeException();
227 RolapCube cube = null;
228
229 DrillThroughVisitor() {
230 }
231
232 public Object visit(MemberExpr memberExpr) {
233 handleMember(memberExpr.getMember());
234 return null;
235 }
236
237 public Object visit(ResolvedFunCall call) {
238 final FunDef def = call.getFunDef();
239 final Exp[] args = call.getArgs();
240 if (def.getName().equals("+")
241 || def.getName().equals("-")
242 || def.getName().equals("/")
243 || def.getName().equals("*")
244 || def.getName().equals("CoalesceEmpty")
245 // Allow parentheses but don't allow tuple
246 || def.getName().equals("()") && args.length == 1) {
247 visitChildren(args);
248 return null;
249 }
250 throw bomb;
251 }
252
253 private void visitChildren(Exp[] args) {
254 for (Exp arg : args) {
255 arg.accept(this);
256 }
257 }
258
259 public void handleMember(Member member) {
260 if (member instanceof RolapStoredMeasure) {
261 // If this member is in a different cube that previous members
262 // we've seen, we cannot drill through.
263 final RolapCube cube = ((RolapStoredMeasure) member).getCube();
264 if (this.cube == null) {
265 this.cube = cube;
266 } else if (this.cube != cube) {
267 // this measure lives in a different cube than previous
268 // measures we have seen
269 throw bomb;
270 }
271 } else if (member instanceof RolapCubeMember) {
272 handleMember(((RolapCubeMember) member).rolapMember);
273 } else if (member instanceof RolapHierarchy.RolapCalculatedMeasure) {
274 RolapHierarchy.RolapCalculatedMeasure measure =
275 (RolapHierarchy.RolapCalculatedMeasure) member;
276 measure.getFormula().getExpression().accept(this);
277 } else if (member instanceof RolapMember) {
278 // regular RolapMember - fine
279 } else {
280 // don't know what this is!
281 throw bomb;
282 }
283 }
284
285 public Object visit(NamedSetExpr namedSetExpr) {
286 throw Util.newInternal("not valid here: " + namedSetExpr);
287 }
288
289 public Object visit(Literal literal) {
290 return null; // literals are drillable
291 }
292
293 public Object visit(Query query) {
294 throw Util.newInternal("not valid here: " + query);
295 }
296
297 public Object visit(QueryAxis queryAxis) {
298 throw Util.newInternal("not valid here: " + queryAxis);
299 }
300
301 public Object visit(Formula formula) {
302 throw Util.newInternal("not valid here: " + formula);
303 }
304
305 public Object visit(UnresolvedFunCall call) {
306 throw Util.newInternal("expected resolved expression");
307 }
308
309 public Object visit(Id id) {
310 throw Util.newInternal("expected resolved expression");
311 }
312
313 public Object visit(ParameterExpr parameterExpr) {
314 // Not valid in general; might contain complex expression
315 throw bomb;
316 }
317
318 public Object visit(DimensionExpr dimensionExpr) {
319 // Not valid in general; might be part of complex expression
320 throw bomb;
321 }
322
323 public Object visit(HierarchyExpr hierarchyExpr) {
324 // Not valid in general; might be part of complex expression
325 throw bomb;
326 }
327
328 public Object visit(LevelExpr levelExpr) {
329 // Not valid in general; might be part of complex expression
330 throw bomb;
331 }
332 }
333 }
334
335 // End RolapCell.java