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) 2004-2005 TONBELLER AG 008// Copyright (C) 2006-2013 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.mdx.MemberExpr; 014import mondrian.mdx.ResolvedFunCall; 015import mondrian.olap.*; 016import mondrian.rolap.RestrictedMemberReader.MultiCardinalityDefaultMember; 017import mondrian.rolap.RolapHierarchy.LimitedRollupMember; 018import mondrian.rolap.aggmatcher.AggStar; 019import mondrian.rolap.sql.*; 020 021import java.util.*; 022 023/** 024 * limits the result of a Member SQL query to the current evaluation context. 025 * All Members of the current context are joined against the fact table and only 026 * those rows are returned, that have an entry in the fact table. 027 * 028 * <p>For example, if you have two dimensions, "invoice" and "time", and the 029 * current context (e.g. the slicer) contains a day from the "time" dimension, 030 * then only the invoices of that day are found. Used to optimize NON EMPTY. 031 * 032 * <p> The {@link TupleConstraint} methods may silently ignore calculated 033 * members (depends on the <code>strict</code> c'tor argument), so these may 034 * return more members than the current context restricts to. The 035 * MemberChildren methods will never accept calculated members as parents, 036 * these will cause an exception. 037 * 038 * @author av 039 * @since Nov 2, 2005 040 */ 041public class SqlContextConstraint 042 implements MemberChildrenConstraint, TupleConstraint 043{ 044 private final List<Object> cacheKey; 045 private Evaluator evaluator; 046 private boolean strict; 047 048 /** 049 * @param context evaluation context 050 * @param strict false if more rows than requested may be returned 051 * (i.e. the constraint is incomplete) 052 * 053 * @return false if this contstraint will not work for the current context 054 */ 055 public static boolean isValidContext(Evaluator context, boolean strict) { 056 return isValidContext(context, true, null, strict); 057 } 058 059 /** 060 * @param context evaluation context 061 * @param disallowVirtualCube if true, check for virtual cubes 062 * @param levels levels being referenced in the current context 063 * @param strict false if more rows than requested may be returned 064 * (i.e. the constraint is incomplete) 065 * 066 * @return false if constraint will not work for current context 067 */ 068 public static boolean isValidContext( 069 Evaluator context, 070 boolean disallowVirtualCube, 071 Level [] levels, 072 boolean strict) 073 { 074 if (context == null) { 075 return false; 076 } 077 RolapCube cube = (RolapCube) context.getCube(); 078 if (disallowVirtualCube) { 079 if (cube.isVirtual()) { 080 return false; 081 } 082 } 083 if (cube.isVirtual()) { 084 Query query = context.getQuery(); 085 Set<RolapCube> baseCubes = new HashSet<RolapCube>(); 086 List<RolapCube> baseCubeList = new ArrayList<RolapCube>(); 087 if (!findVirtualCubeBaseCubes(query, baseCubes, baseCubeList)) { 088 return false; 089 } 090 assert levels != null; 091 query.setBaseCubes(baseCubeList); 092 } 093 094 // may return more rows than requested? 095 if (!strict) { 096 return true; 097 } 098 099 // we can not handle all calc members in slicer. Calc measure and some 100 // like aggregates are exceptions 101 Member[] members = context.getMembers(); 102 for (int i = 1; i < members.length; i++) { 103 if (members[i].isCalculated() 104 && !SqlConstraintUtils.isSupportedCalculatedMember(members[i])) 105 { 106 return false; 107 } 108 } 109 return true; 110 } 111 112 /** 113 * Locates base cubes related to the measures referenced in the query. 114 * 115 * @param query query referencing the virtual cube 116 * @param baseCubes set of base cubes 117 * 118 * @return true if valid measures exist 119 */ 120 private static boolean findVirtualCubeBaseCubes( 121 Query query, 122 Set<RolapCube> baseCubes, 123 List<RolapCube> baseCubeList) 124 { 125 // Gather the unique set of level-to-column maps corresponding 126 // to the underlying star/cube where the measure column 127 // originates from. 128 Set<Member> measureMembers = query.getMeasuresMembers(); 129 // if no measures are explicitly referenced, just use the default 130 // measure 131 if (measureMembers.isEmpty()) { 132 Cube cube = query.getCube(); 133 Dimension dimension = cube.getDimensions()[0]; 134 query.addMeasuresMembers( 135 dimension.getHierarchy().getDefaultMember()); 136 } 137 for (Member member : query.getMeasuresMembers()) { 138 if (member instanceof RolapStoredMeasure) { 139 addMeasure( 140 (RolapStoredMeasure) member, baseCubes, baseCubeList); 141 } else if (member instanceof RolapCalculatedMember) { 142 findMeasures(member.getExpression(), baseCubes, baseCubeList); 143 } 144 } 145 if (baseCubes.isEmpty()) { 146 return false; 147 } 148 return true; 149 } 150 151 /** 152 * Adds information regarding a stored measure to maps 153 * 154 * @param measure the stored measure 155 * @param baseCubes set of base cubes 156 */ 157 private static void addMeasure( 158 RolapStoredMeasure measure, 159 Set<RolapCube> baseCubes, 160 List<RolapCube> baseCubeList) 161 { 162 RolapCube baseCube = measure.getCube(); 163 if (baseCubes.add(baseCube)) { 164 baseCubeList.add(baseCube); 165 } 166 } 167 168 /** 169 * Extracts the stored measures referenced in an expression 170 * 171 * @param exp expression 172 * @param baseCubes set of base cubes 173 */ 174 private static void findMeasures( 175 Exp exp, 176 Set<RolapCube> baseCubes, 177 List<RolapCube> baseCubeList) 178 { 179 if (exp instanceof MemberExpr) { 180 MemberExpr memberExpr = (MemberExpr) exp; 181 Member member = memberExpr.getMember(); 182 if (member instanceof RolapStoredMeasure) { 183 addMeasure( 184 (RolapStoredMeasure) member, baseCubes, baseCubeList); 185 } else if (member instanceof RolapCalculatedMember) { 186 findMeasures(member.getExpression(), baseCubes, baseCubeList); 187 } 188 } else if (exp instanceof ResolvedFunCall) { 189 ResolvedFunCall funCall = (ResolvedFunCall) exp; 190 Exp [] args = funCall.getArgs(); 191 for (Exp arg : args) { 192 findMeasures(arg, baseCubes, baseCubeList); 193 } 194 } 195 } 196 197 /** 198 * Creates a SqlContextConstraint. 199 * 200 * @param evaluator Evaluator 201 * @param strict defines the behaviour if the evaluator context 202 * contains calculated members. If true, an exception is thrown, 203 * otherwise calculated members are silently ignored. The 204 * methods {@link mondrian.rolap.sql.MemberChildrenConstraint#addMemberConstraint(mondrian.rolap.sql.SqlQuery, mondrian.rolap.RolapCube, mondrian.rolap.aggmatcher.AggStar, RolapMember)} and 205 * {@link mondrian.rolap.sql.MemberChildrenConstraint#addMemberConstraint(mondrian.rolap.sql.SqlQuery, mondrian.rolap.RolapCube, mondrian.rolap.aggmatcher.AggStar, java.util.List)} will 206 * never accept a calculated member as parent. 207 */ 208 SqlContextConstraint(RolapEvaluator evaluator, boolean strict) { 209 this.evaluator = evaluator.push(); 210 this.strict = strict; 211 cacheKey = new ArrayList<Object>(); 212 cacheKey.add(getClass()); 213 cacheKey.add(strict); 214 215 List<Member> members = new ArrayList<Member>(); 216 List<Member> expandedMembers = new ArrayList<Member>(); 217 218 members.addAll( 219 Arrays.asList( 220 SqlConstraintUtils.removeMultiPositionSlicerMembers( 221 evaluator.getMembers(), evaluator))); 222 223 // Now we'll need to expand the aggregated members 224 expandedMembers.addAll( 225 Arrays.asList( 226 SqlConstraintUtils.expandSupportedCalculatedMembers( 227 members, 228 evaluator))); 229 cacheKey.add(expandedMembers); 230 231 // Add restrictions imposed by Role based access filtering 232 Map<Level, List<RolapMember>> roleMembers = 233 SqlConstraintUtils.getRoleConstraintMembers( 234 this.getEvaluator().getSchemaReader(), 235 this.getEvaluator().getMembers()); 236 for (List<RolapMember> list : roleMembers.values()) { 237 cacheKey.addAll(list); 238 } 239 240 // For virtual cubes, context constraint should be evaluated in the 241 // query's context, because the query might reference different base 242 // cubes. 243 // 244 // Note: we could avoid adding base cubes to the key if the evaluator 245 // contains measure members referenced in the query, rather than 246 // just the default measure for the entire virtual cube. The commented 247 // code in RolapResult() that replaces the default measure seems to 248 // do that. 249 if (evaluator.getCube().isVirtual()) { 250 cacheKey.addAll(evaluator.getQuery().getBaseCubes()); 251 } 252 } 253 254 /** 255 * Called from MemberChildren: adds <code>parent</code> to the current 256 * context and restricts the SQL resultset to that new context. 257 */ 258 public void addMemberConstraint( 259 SqlQuery sqlQuery, 260 RolapCube baseCube, 261 AggStar aggStar, 262 RolapMember parent) 263 { 264 if (parent.isCalculated()) { 265 throw Util.newInternal("cannot restrict SQL to calculated member"); 266 } 267 final int savepoint = evaluator.savepoint(); 268 try { 269 evaluator.setContext(parent); 270 SqlConstraintUtils.addContextConstraint( 271 sqlQuery, aggStar, evaluator, baseCube, strict); 272 } finally { 273 evaluator.restore(savepoint); 274 } 275 276 // comment out addMemberConstraint here since constraint 277 // is already added by addContextConstraint 278 // SqlConstraintUtils.addMemberConstraint( 279 // sqlQuery, baseCube, aggStar, parent, true); 280 } 281 282 /** 283 * Adds <code>parents</code> to the current 284 * context and restricts the SQL resultset to that new context. 285 */ 286 public void addMemberConstraint( 287 SqlQuery sqlQuery, 288 RolapCube baseCube, 289 AggStar aggStar, 290 List<RolapMember> parents) 291 { 292 SqlConstraintUtils.addContextConstraint( 293 sqlQuery, aggStar, evaluator, baseCube, strict); 294 boolean exclude = false; 295 SqlConstraintUtils.addMemberConstraint( 296 sqlQuery, baseCube, aggStar, parents, true, false, exclude); 297 } 298 299 /** 300 * Called from LevelMembers: restricts the SQL resultset to the current 301 * context. 302 */ 303 public void addConstraint( 304 SqlQuery sqlQuery, 305 RolapCube baseCube, 306 AggStar aggStar) 307 { 308 SqlConstraintUtils.addContextConstraint( 309 sqlQuery, aggStar, evaluator, baseCube, strict); 310 } 311 312 /** 313 * Returns whether a join with the fact table is required. A join is 314 * required if the context contains members from dimensions other than 315 * level. If we are interested in the members of a level or a members 316 * children then it does not make sense to join only one dimension (the one 317 * that contains the requested members) with the fact table for NON EMPTY 318 * optimization. 319 */ 320 protected boolean isJoinRequired() { 321 Member[] members = evaluator.getMembers(); 322 // members[0] is the Measure, so loop starts at 1 323 for (int i = 1; i < members.length; i++) { 324 if (!members[i].isAll() 325 || members[i] instanceof LimitedRollupMember 326 || members[i] instanceof MultiCardinalityDefaultMember) 327 { 328 return true; 329 } 330 } 331 return false; 332 } 333 334 public void addLevelConstraint( 335 SqlQuery sqlQuery, 336 RolapCube baseCube, 337 AggStar aggStar, 338 RolapLevel level) 339 { 340 if (!isJoinRequired()) { 341 return; 342 } 343 SqlConstraintUtils.joinLevelTableToFactTable( 344 sqlQuery, baseCube, aggStar, evaluator, (RolapCubeLevel)level); 345 } 346 347 public MemberChildrenConstraint getMemberChildrenConstraint( 348 RolapMember parent) 349 { 350 return this; 351 } 352 353 public Object getCacheKey() { 354 return cacheKey; 355 } 356 357 public Evaluator getEvaluator() { 358 return evaluator; 359 } 360} 361 362// End SqlContextConstraint.java 363