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