001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapCell.java#28 $ 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-2008 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/src/main/mondrian/rolap/RolapCell.java#28 $ 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