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) 2005-2005 Julian Hyde 009// Copyright (C) 2005-2013 Pentaho and others 010// All Rights Reserved. 011*/ 012package mondrian.rolap; 013 014import mondrian.calc.ResultStyle; 015import mondrian.calc.TupleList; 016import mondrian.calc.impl.DelegatingTupleList; 017import mondrian.olap.*; 018import mondrian.rolap.TupleReader.MemberBuilder; 019import mondrian.rolap.aggmatcher.AggStar; 020import mondrian.rolap.cache.*; 021import mondrian.rolap.sql.*; 022 023import org.apache.log4j.Logger; 024 025import java.util.*; 026 027import javax.sql.DataSource; 028 029/** 030 * Analyses set expressions and executes them in SQL if possible. 031 * Supports crossjoin, member.children, level.members and member.descendants - 032 * all in non empty mode, i.e. there is a join to the fact table.<p/> 033 * 034 * <p>TODO: the order of the result is different from the order of the 035 * enumeration. Should sort. 036 * 037 * @author av 038 * @since Nov 12, 2005 039 */ 040public abstract class RolapNativeSet extends RolapNative { 041 protected static final Logger LOGGER = 042 Logger.getLogger(RolapNativeSet.class); 043 044 private SmartCache<Object, TupleList> cache = 045 new SoftSmartCache<Object, TupleList>(); 046 047 /** 048 * Returns whether certain member types (e.g. calculated members) should 049 * disable native SQL evaluation for expressions containing them. 050 * 051 * <p>If true, expressions containing calculated members will be evaluated 052 * by the interpreter, instead of using SQL. 053 * 054 * <p>If false, calc members will be ignored and the computation will be 055 * done in SQL, returning more members than requested. This is ok, if 056 * the superflous members are filtered out in java code afterwards. 057 * 058 * @return whether certain member types should disable native SQL evaluation 059 */ 060 protected abstract boolean restrictMemberTypes(); 061 062 protected CrossJoinArgFactory crossJoinArgFactory() { 063 return new CrossJoinArgFactory(restrictMemberTypes()); 064 } 065 066 /** 067 * Constraint for non empty {crossjoin, member.children, 068 * member.descendants, level.members} 069 */ 070 protected static abstract class SetConstraint extends SqlContextConstraint { 071 CrossJoinArg[] args; 072 073 SetConstraint( 074 CrossJoinArg[] args, 075 RolapEvaluator evaluator, 076 boolean strict) 077 { 078 super(evaluator, strict); 079 this.args = args; 080 } 081 082 /** 083 * {@inheritDoc} 084 * 085 * <p>If there is a crossjoin, we need to join the fact table - even if 086 * the evaluator context is empty. 087 */ 088 protected boolean isJoinRequired() { 089 return args.length > 1 || super.isJoinRequired(); 090 } 091 092 public void addConstraint( 093 SqlQuery sqlQuery, 094 RolapCube baseCube, 095 AggStar aggStar) 096 { 097 super.addConstraint(sqlQuery, baseCube, aggStar); 098 for (CrossJoinArg arg : args) { 099 // if the cross join argument has calculated members in its 100 // enumerated set, ignore the constraint since we won't 101 // produce that set through the native sql and instead 102 // will simply enumerate through the members in the set 103 if (!(arg instanceof MemberListCrossJoinArg) 104 || !((MemberListCrossJoinArg) arg).hasCalcMembers()) 105 { 106 RolapLevel level = arg.getLevel(); 107 if (level == null || levelIsOnBaseCube(baseCube, level)) { 108 arg.addConstraint(sqlQuery, baseCube, aggStar); 109 } 110 } 111 } 112 } 113 114 private boolean levelIsOnBaseCube( 115 final RolapCube baseCube, final RolapLevel level) 116 { 117 return baseCube.findBaseCubeHierarchy(level.getHierarchy()) != null; 118 } 119 120 /** 121 * Returns null to prevent the member/childern from being cached. There 122 * exists no valid MemberChildrenConstraint that would fetch those 123 * children that were extracted as a side effect from evaluating a non 124 * empty crossjoin 125 */ 126 public MemberChildrenConstraint getMemberChildrenConstraint( 127 RolapMember parent) 128 { 129 return null; 130 } 131 132 /** 133 * returns a key to cache the result 134 */ 135 public Object getCacheKey() { 136 List<Object> key = new ArrayList<Object>(); 137 key.add(super.getCacheKey()); 138 // only add args that will be retrieved through native sql; 139 // args that are sets with calculated members aren't executed 140 // natively 141 for (CrossJoinArg arg : args) { 142 if (!(arg instanceof MemberListCrossJoinArg) 143 || !((MemberListCrossJoinArg) arg).hasCalcMembers()) 144 { 145 key.add(arg); 146 } 147 } 148 return key; 149 } 150 } 151 152 protected class SetEvaluator implements NativeEvaluator { 153 private final CrossJoinArg[] args; 154 private final SchemaReaderWithMemberReaderAvailable schemaReader; 155 private final TupleConstraint constraint; 156 private int maxRows = 0; 157 158 public SetEvaluator( 159 CrossJoinArg[] args, 160 SchemaReader schemaReader, 161 TupleConstraint constraint) 162 { 163 this.args = args; 164 if (schemaReader instanceof SchemaReaderWithMemberReaderAvailable) { 165 this.schemaReader = 166 (SchemaReaderWithMemberReaderAvailable) schemaReader; 167 } else { 168 this.schemaReader = 169 new SchemaReaderWithMemberReaderCache(schemaReader); 170 } 171 this.constraint = constraint; 172 } 173 174 public Object execute(ResultStyle desiredResultStyle) { 175 switch (desiredResultStyle) { 176 case ITERABLE: 177 for (CrossJoinArg arg : this.args) { 178 if (arg.getLevel().getDimension().isHighCardinality()) { 179 // If any of the dimensions is a HCD, 180 // use the proper tuple reader. 181 return executeList( 182 new HighCardSqlTupleReader(constraint)); 183 } 184 // Use the regular tuple reader. 185 return executeList( 186 new SqlTupleReader(constraint)); 187 } 188 case MUTABLE_LIST: 189 case LIST: 190 return executeList(new SqlTupleReader(constraint)); 191 default: 192 throw ResultStyleException.generate( 193 ResultStyle.ITERABLE_MUTABLELIST_LIST, 194 Collections.singletonList(desiredResultStyle)); 195 } 196 } 197 198 protected TupleList executeList(final SqlTupleReader tr) { 199 tr.setMaxRows(maxRows); 200 for (CrossJoinArg arg : args) { 201 addLevel(tr, arg); 202 } 203 204 // Look up the result in cache; we can't return the cached 205 // result if the tuple reader contains a target with calculated 206 // members because the cached result does not include those 207 // members; so we still need to cross join the cached result 208 // with those enumerated members. 209 // 210 // The key needs to include the arguments (projection) as well as 211 // the constraint, because it's possible (see bug MONDRIAN-902) 212 // that independent axes have identical constraints but different 213 // args (i.e. projections). REVIEW: In this case, should we use the 214 // same cached result and project different columns? 215 List<Object> key = new ArrayList<Object>(); 216 key.add(tr.getCacheKey()); 217 key.addAll(Arrays.asList(args)); 218 key.add(maxRows); 219 220 TupleList result = cache.get(key); 221 boolean hasEnumTargets = (tr.getEnumTargetCount() > 0); 222 if (result != null && !hasEnumTargets) { 223 if (listener != null) { 224 TupleEvent e = new TupleEvent(this, tr); 225 listener.foundInCache(e); 226 } 227 return new DelegatingTupleList( 228 args.length, Util.<List<Member>>cast(result)); 229 } 230 231 // execute sql and store the result 232 if (result == null && listener != null) { 233 TupleEvent e = new TupleEvent(this, tr); 234 listener.executingSql(e); 235 } 236 237 // if we don't have a cached result in the case where we have 238 // enumerated targets, then retrieve and cache that partial result 239 TupleList partialResult = result; 240 List<List<RolapMember>> newPartialResult = null; 241 if (hasEnumTargets && partialResult == null) { 242 newPartialResult = new ArrayList<List<RolapMember>>(); 243 } 244 DataSource dataSource = schemaReader.getDataSource(); 245 if (args.length == 1) { 246 result = 247 tr.readMembers( 248 dataSource, partialResult, newPartialResult); 249 } else { 250 result = 251 tr.readTuples( 252 dataSource, partialResult, newPartialResult); 253 } 254 255 if (!MondrianProperties.instance().DisableCaching.get()) { 256 if (hasEnumTargets) { 257 if (newPartialResult != null) { 258 cache.put( 259 key, 260 new DelegatingTupleList( 261 args.length, 262 Util.<List<Member>>cast(newPartialResult))); 263 } 264 } else { 265 cache.put(key, result); 266 } 267 } 268 return result; 269 } 270 271 private void addLevel(TupleReader tr, CrossJoinArg arg) { 272 RolapLevel level = arg.getLevel(); 273 if (level == null) { 274 // Level can be null if the CrossJoinArg represent 275 // an empty set. 276 // This is used to push down the "1 = 0" predicate 277 // into the emerging CJ so that the entire CJ can 278 // be natively evaluated. 279 return; 280 } 281 282 RolapHierarchy hierarchy = level.getHierarchy(); 283 MemberReader mr = schemaReader.getMemberReader(hierarchy); 284 MemberBuilder mb = mr.getMemberBuilder(); 285 Util.assertTrue(mb != null, "MemberBuilder not found"); 286 287 if (arg instanceof MemberListCrossJoinArg 288 && ((MemberListCrossJoinArg) arg).hasCalcMembers()) 289 { 290 // only need to keep track of the members in the case 291 // where there are calculated members since in that case, 292 // we produce the values by enumerating through the list 293 // rather than generating the values through native sql 294 tr.addLevelMembers(level, mb, arg.getMembers()); 295 } else { 296 tr.addLevelMembers(level, mb, null); 297 } 298 } 299 300 int getMaxRows() { 301 return maxRows; 302 } 303 304 void setMaxRows(int maxRows) { 305 this.maxRows = maxRows; 306 } 307 } 308 309 /** 310 * Tests whether non-native evaluation is preferred for the 311 * given arguments. 312 * 313 * @param joinArg true if evaluating a cross-join; false if 314 * evaluating a single-input expression such as filter 315 * 316 * @return true if <em>all</em> args prefer the interpreter 317 */ 318 protected boolean isPreferInterpreter( 319 CrossJoinArg[] args, 320 boolean joinArg) 321 { 322 for (CrossJoinArg arg : args) { 323 if (!arg.isPreferInterpreter(joinArg)) { 324 return false; 325 } 326 } 327 return true; 328 } 329 330 /** disable garbage collection for test */ 331 @SuppressWarnings({ "unchecked", "rawtypes" }) 332 void useHardCache(boolean hard) { 333 if (hard) { 334 cache = new HardSmartCache(); 335 } else { 336 cache = new SoftSmartCache(); 337 } 338 } 339 340 /** 341 * Overrides current members in position by default members in 342 * hierarchies which are involved in this filter/topcount. 343 * Stores the RolapStoredMeasure into the context because that is needed to 344 * generate a cell request to constraint the sql. 345 * 346 * <p>The current context may contain a calculated measure, this measure 347 * was translated into an sql condition (filter/topcount). The measure 348 * is not used to constrain the result but only to access the star. 349 * 350 * @param evaluator Evaluation context to modify 351 * @param cargs Cross join arguments 352 * @param storedMeasure Stored measure 353 * 354 * @see RolapAggregationManager#makeRequest(RolapEvaluator) 355 */ 356 protected void overrideContext( 357 RolapEvaluator evaluator, 358 CrossJoinArg[] cargs, 359 RolapStoredMeasure storedMeasure) 360 { 361 SchemaReader schemaReader = evaluator.getSchemaReader(); 362 for (CrossJoinArg carg : cargs) { 363 RolapLevel level = carg.getLevel(); 364 if (level != null) { 365 RolapHierarchy hierarchy = level.getHierarchy(); 366 367 final Member contextMember; 368 if (hierarchy.hasAll() 369 || schemaReader.getRole() 370 .getAccess(hierarchy) == Access.ALL) 371 { 372 // The hierarchy may have access restrictions. 373 // If it does, calling .substitute() will retrieve an 374 // appropriate LimitedRollupMember. 375 contextMember = 376 schemaReader.substitute(hierarchy.getAllMember()); 377 } else { 378 // If there is no All member on a role restricted hierarchy, 379 // use a restricted member based on the set of accessible 380 // root members. 381 contextMember = new RestrictedMemberReader 382 .MultiCardinalityDefaultMember( 383 hierarchy.getMemberReader() 384 .getRootMembers().get(0)); 385 } 386 evaluator.setContext(contextMember); 387 } 388 } 389 if (storedMeasure != null) { 390 evaluator.setContext(storedMeasure); 391 } 392 } 393 394 395 public interface SchemaReaderWithMemberReaderAvailable 396 extends SchemaReader 397 { 398 MemberReader getMemberReader(Hierarchy hierarchy); 399 } 400 401 private static class SchemaReaderWithMemberReaderCache 402 extends DelegatingSchemaReader 403 implements SchemaReaderWithMemberReaderAvailable 404 { 405 private final Map<Hierarchy, MemberReader> hierarchyReaders = 406 new HashMap<Hierarchy, MemberReader>(); 407 408 SchemaReaderWithMemberReaderCache(SchemaReader schemaReader) { 409 super(schemaReader); 410 } 411 412 public synchronized MemberReader getMemberReader(Hierarchy hierarchy) { 413 MemberReader memberReader = hierarchyReaders.get(hierarchy); 414 if (memberReader == null) { 415 memberReader = 416 ((RolapHierarchy) hierarchy).createMemberReader( 417 schemaReader.getRole()); 418 hierarchyReaders.put(hierarchy, memberReader); 419 } 420 return memberReader; 421 } 422 } 423} 424 425// End RolapNativeSet.java 426