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.calc.*; 014 import mondrian.olap.*; 015 import mondrian.rolap.TupleReader.MemberBuilder; 016 import mondrian.rolap.cache.HardSmartCache; 017 import mondrian.rolap.cache.SmartCache; 018 import mondrian.rolap.cache.SoftSmartCache; 019 import mondrian.rolap.sql.MemberChildrenConstraint; 020 import mondrian.rolap.sql.SqlQuery; 021 import mondrian.rolap.sql.TupleConstraint; 022 import mondrian.mdx.*; 023 024 import org.apache.log4j.Logger; 025 026 import javax.sql.DataSource; 027 028 /** 029 * Analyses set expressions and executes them in SQL if possible. 030 * Supports crossjoin, member.children, level.members and member.descendants - 031 * all in non empty mode, i.e. there is a join to the fact table.<p/> 032 * 033 * TODO: the order of the result is different from the order of the 034 * enumeration. Should sort. 035 * 036 * @author av 037 * @since Nov 12, 2005 038 * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapNativeSet.java#44 $ 039 */ 040 public abstract class RolapNativeSet extends RolapNative { 041 protected static final Logger LOGGER = Logger.getLogger(RolapNativeSet.class); 042 043 private SmartCache<Object, List<List<RolapMember>>> cache = 044 new SoftSmartCache<Object, List<List<RolapMember>>>(); 045 046 /** 047 * Returns whether certain member types(e.g. calculated members) should 048 * disable native SQL evaluation for expressions containing them. 049 * 050 * <p> 051 * If true, expressions containing calculated members will be evaluated by 052 * the interpreter, instead of using SQL. 053 * 054 * If false, calc members will be ignored and the computation will be done 055 * in SQL, returning more members than requested. 056 * </p> 057 */ 058 protected abstract boolean restrictMemberTypes(); 059 060 /** 061 * Constraint for non empty {crossjoin, member.children, 062 * member.descendants, level.members} 063 */ 064 protected static abstract class SetConstraint extends SqlContextConstraint { 065 CrossJoinArg[] args; 066 067 SetConstraint(CrossJoinArg[] args, RolapEvaluator evaluator, boolean strict) { 068 super(evaluator, strict); 069 this.args = args; 070 } 071 072 /** 073 * if there is a crossjoin, we need to join the fact table - even if the 074 * evalutaor context is empty. 075 */ 076 protected boolean isJoinRequired() { 077 return args.length > 1 || super.isJoinRequired(); 078 } 079 080 public void addConstraint(SqlQuery sqlQuery, RolapCube baseCube) { 081 super.addConstraint(sqlQuery, baseCube); 082 for (CrossJoinArg arg : args) { 083 // if the cross join argument has calculated members in its 084 // enumerated set, ignore the constraint since we won't 085 // produce that set through the native sql and instead 086 // will simply enumerate through the members in the set 087 if (!(arg instanceof MemberListCrossJoinArg) || 088 !((MemberListCrossJoinArg) arg).hasCalcMembers()) { 089 arg.addConstraint(sqlQuery, baseCube); 090 } 091 } 092 } 093 094 /** 095 * Returns null to prevent the member/childern from being cached. There 096 * exists no valid MemberChildrenConstraint that would fetch those 097 * children that were extracted as a side effect from evaluating a non 098 * empty crossjoin 099 */ 100 public MemberChildrenConstraint getMemberChildrenConstraint( 101 RolapMember parent) { 102 return null; 103 } 104 105 /** 106 * returns a key to cache the result 107 */ 108 public Object getCacheKey() { 109 List<Object> key = new ArrayList<Object>(); 110 key.add(super.getCacheKey()); 111 // only add args that will be retrieved through native sql; 112 // args that are sets with calculated members aren't executed 113 // natively 114 for (CrossJoinArg arg : args) { 115 if (!(arg instanceof MemberListCrossJoinArg) || 116 !((MemberListCrossJoinArg) arg).hasCalcMembers()) { 117 key.add(arg); 118 } 119 } 120 return key; 121 } 122 } 123 124 protected class SetEvaluator implements NativeEvaluator { 125 private final CrossJoinArg[] args; 126 private final SchemaReaderWithMemberReaderAvailable schemaReader; 127 private final TupleConstraint constraint; 128 private int maxRows = 0; 129 130 public SetEvaluator( 131 CrossJoinArg[] args, 132 SchemaReader schemaReader, 133 TupleConstraint constraint) 134 { 135 this.args = args; 136 if (schemaReader instanceof SchemaReaderWithMemberReaderAvailable) { 137 this.schemaReader = 138 (SchemaReaderWithMemberReaderAvailable) schemaReader; 139 } else { 140 this.schemaReader = 141 new SchemaReaderWithMemberReaderCache(schemaReader); 142 } 143 this.constraint = constraint; 144 } 145 146 public Object execute(ResultStyle desiredResultStyle) { 147 switch (desiredResultStyle) { 148 case ITERABLE: 149 return executeList(new HighCardSqlTupleReader(constraint)); 150 case MUTABLE_LIST: 151 case LIST: 152 return executeList(new SqlTupleReader(constraint)); 153 } 154 throw ResultStyleException.generate( 155 ResultStyle.ITERABLE_MUTABLELIST_LIST, 156 Collections.singletonList(desiredResultStyle)); 157 } 158 159 protected List executeList(final SqlTupleReader tr) { 160 tr.setMaxRows(maxRows); 161 for (CrossJoinArg arg : args) { 162 addLevel(tr, arg); 163 } 164 165 // lookup the result in cache; we can't return the cached 166 // result if the tuple reader contains a target with calculated 167 // members because the cached result does not include those 168 // members; so we still need to cross join the cached result 169 // with those enumerated members 170 Object key = tr.getCacheKey(); 171 List<List<RolapMember>> result = cache.get(key); 172 boolean hasEnumTargets = (tr.getEnumTargetCount() > 0); 173 if (result != null && !hasEnumTargets) { 174 if (listener != null) { 175 TupleEvent e = new TupleEvent(this, tr); 176 listener.foundInCache(e); 177 } 178 return copy(result); 179 } 180 181 // execute sql and store the result 182 if (result == null && listener != null) { 183 TupleEvent e = new TupleEvent(this, tr); 184 listener.excutingSql(e); 185 } 186 187 // if we don't have a cached result in the case where we have 188 // enumerated targets, then retrieve and cache that partial result 189 List<List<RolapMember>> partialResult = result; 190 result = null; 191 List<List<RolapMember>> newPartialResult = null; 192 if (hasEnumTargets && partialResult == null) { 193 newPartialResult = new ArrayList<List<RolapMember>>(); 194 } 195 DataSource dataSource = schemaReader.getDataSource(); 196 if (args.length == 1) { 197 result = (List) tr.readMembers(dataSource, partialResult, newPartialResult); 198 } else { 199 result = (List) tr.readTuples(dataSource, partialResult, newPartialResult); 200 } 201 202 if (hasEnumTargets) { 203 if (newPartialResult != null) { 204 cache.put(key, newPartialResult); 205 } 206 } else { 207 cache.put(key, result); 208 } 209 return copy(result); 210 } 211 212 /** 213 * returns a copy of the result because its modified 214 */ 215 private <T> List<T> copy(List<T> list) { 216 // return new ArrayList<T>(list); 217 return list; 218 } 219 220 private void addLevel(TupleReader tr, CrossJoinArg arg) { 221 RolapLevel level = arg.getLevel(); 222 if (level == null) { 223 // Level can be null if the CrossJoinArg represent 224 // an empty set. 225 // This is used to push down the "1 = 0" predicate 226 // into the emerging CJ so that the entire CJ can 227 // be natively evaluated. 228 return; 229 } 230 231 RolapHierarchy hierarchy = level.getHierarchy(); 232 MemberReader mr = schemaReader.getMemberReader(hierarchy); 233 MemberBuilder mb = mr.getMemberBuilder(); 234 Util.assertTrue(mb != null, "MemberBuilder not found"); 235 236 if (arg instanceof MemberListCrossJoinArg && 237 ((MemberListCrossJoinArg) arg).hasCalcMembers()) 238 { 239 // only need to keep track of the members in the case 240 // where there are calculated members since in that case, 241 // we produce the values by enumerating through the list 242 // rather than generating the values through native sql 243 tr.addLevelMembers(level, mb, arg.getMembers()); 244 } else { 245 tr.addLevelMembers(level, mb, null); 246 } 247 } 248 249 int getMaxRows() { 250 return maxRows; 251 } 252 253 void setMaxRows(int maxRows) { 254 this.maxRows = maxRows; 255 } 256 } 257 258 /** 259 * "Light version" of a {@link TupleConstraint}, represents one of 260 * member.children, level.members, member.descendants, {enumeration}. 261 * 262 * @author av 263 * @since Nov 14, 2005 264 */ 265 protected interface CrossJoinArg { 266 RolapLevel getLevel(); 267 268 List<RolapMember> getMembers(); 269 270 void addConstraint(SqlQuery sqlQuery, RolapCube baseCube); 271 272 boolean isPreferInterpreter(boolean joinArg); 273 } 274 275 /** 276 * represents one of 277 * <ul> 278 * <li>Level.Members: member == null and level != null</li> 279 * <li>Member.Children: member != null and level = member.getLevel().getChildLevel() </li> 280 * <li>Member.Descendants: member != null and level == some level below member.getLevel()</li> 281 * </ul> 282 * 283 * @author av 284 * @since Nov 12, 2005 285 */ 286 protected static class DescendantsCrossJoinArg implements CrossJoinArg { 287 RolapMember member; 288 RolapLevel level; 289 290 public DescendantsCrossJoinArg(RolapLevel level, RolapMember member) { 291 this.level = level; 292 this.member = member; 293 } 294 295 public RolapLevel getLevel() { 296 return level; 297 } 298 299 public List<RolapMember> getMembers() { 300 if (member == null) { 301 return null; 302 } 303 final List<RolapMember> list = new ArrayList<RolapMember>(); 304 list.add(member); 305 return list; 306 } 307 308 public void addConstraint(SqlQuery sqlQuery, RolapCube baseCube) { 309 if (member != null) { 310 SqlConstraintUtils.addMemberConstraint( 311 sqlQuery, baseCube, null, member, true); 312 } 313 } 314 315 public boolean isPreferInterpreter(boolean joinArg) { 316 return false; 317 } 318 319 private boolean equals(Object o1, Object o2) { 320 return o1 == null ? o2 == null : o1.equals(o2); 321 } 322 323 public boolean equals(Object obj) { 324 if (!(obj instanceof DescendantsCrossJoinArg)) { 325 return false; 326 } 327 DescendantsCrossJoinArg that = (DescendantsCrossJoinArg) obj; 328 if (!equals(this.level, that.level)) { 329 return false; 330 } 331 return equals(this.member, that.member); 332 } 333 334 public int hashCode() { 335 int c = 1; 336 if (level != null) { 337 c = level.hashCode(); 338 } 339 if (member != null) { 340 c = 31 * c + member.hashCode(); 341 } 342 return c; 343 } 344 } 345 346 /** 347 * Represents an enumeration {member1, member2, ...}. 348 * All members must to the same level and are non-calculated. 349 * 350 * @author av 351 * @since Nov 14, 2005 352 */ 353 protected static class MemberListCrossJoinArg implements CrossJoinArg { 354 private List<RolapMember> members; 355 private RolapLevel level = null; 356 private boolean restrictMemberTypes; 357 private boolean hasCalcMembers; 358 private boolean hasNonCalcMembers; 359 private boolean hasAllMember; 360 361 private MemberListCrossJoinArg( 362 RolapLevel level, List<RolapMember> members, boolean restrictMemberTypes, 363 boolean hasCalcMembers, boolean hasNonCalcMembers, boolean hasAllMember) { 364 this.level = level; 365 this.members = members; 366 this.restrictMemberTypes = restrictMemberTypes; 367 this.hasCalcMembers = hasCalcMembers; 368 this.hasNonCalcMembers = hasNonCalcMembers; 369 this.hasAllMember = hasAllMember; 370 } 371 372 /** 373 * Creates an instance of {@link RolapNativeSet.CrossJoinArg}, 374 * or returns null if the arguments are invalid. This method also 375 * records properties of the member list such as containing 376 * calc/non calc members, and containing the All member. 377 * 378 * <p>If restrictMemberTypes is set, then the resulting argument could 379 * contain calculated members. The newly created CrossJoinArg is marked 380 * appropriately for special handling downstream. 381 * 382 * <p>If restrictMemberTypes is false, then the resulting argument 383 * contains non-calculated members of the same level (after filtering 384 * out any null members). 385 * 386 * @param evaluator the current evaluator 387 * @param args members in the list 388 * @param restrictMemberTypes whether calculated members are allowed 389 * @return MemberListCrossJoinArg if member list is well formed, 390 * NULL if not. 391 */ 392 static CrossJoinArg create( 393 RolapEvaluator evaluator, 394 final List<RolapMember> args, 395 final boolean restrictMemberTypes) 396 { 397 // First check that the member list will not result in a predicate 398 // longer than the underlying DB could support. 399 if (!isArgSizeSupported(evaluator, args.size())) { 400 return null; 401 } 402 403 RolapLevel level = null; 404 RolapLevel nullLevel = null; 405 boolean hasCalcMembers = false; 406 boolean hasNonCalcMembers = false; 407 408 // Crossjoin Arg is an empty member list. 409 // This is used to push down the constant "false" condition to the 410 // native evaluator. 411 if (args.size() == 0) { 412 hasNonCalcMembers = true; 413 } 414 boolean hasAllMember = false; 415 int nNullMembers = 0; 416 try { 417 for (RolapMember m : args) { 418 if (m.isNull()) { 419 // we're going to filter out null members anyway; 420 // don't choke on the fact that their level 421 // doesn't match that of others 422 nullLevel = m.getLevel(); 423 ++nNullMembers; 424 continue; 425 } 426 427 // If "All" member, native evaluation is not possible 428 // because "All" member does not have a corresponding 429 // relational representation. 430 // 431 // "All" member is ignored during SQL generation. 432 // The complete MDX query can be evaluated natively only 433 // if there is non all member on at least one level; 434 // otherwise the generated SQL is an empty string. 435 // See SqlTupleReader.addLevelMemberSql() 436 // 437 if (m.isAll()) { 438 hasAllMember = true; 439 } 440 441 if (m.isCalculated()) { 442 if (restrictMemberTypes) { 443 return null; 444 } 445 hasCalcMembers = true; 446 } else { 447 hasNonCalcMembers = true; 448 } 449 if (level == null) { 450 level = m.getLevel(); 451 } else if (!level.equals(m.getLevel())) { 452 // Members should be on the same level. 453 return null; 454 } 455 } 456 } catch (ClassCastException cce) { 457 return null; 458 } 459 if (level == null) { 460 // all members were null; use an arbitrary one of the 461 // null levels since the SQL predicate is going to always 462 // fail anyway 463 level = nullLevel; 464 } 465 466 // level will be null for an empty CJ input that is pushed down 467 // to the native evaluator. 468 // This case is not treated as a non-native input. 469 if ((level != null) && (!isSimpleLevel(level))) { 470 return null; 471 } 472 List<RolapMember> members = new ArrayList<RolapMember>(); 473 474 for (RolapMember m : args) { 475 if (m.isNull()) { 476 // filter out null members 477 continue; 478 } 479 members.add(m); 480 } 481 482 return new MemberListCrossJoinArg( 483 level, members, restrictMemberTypes, 484 hasCalcMembers, hasNonCalcMembers, hasAllMember); 485 } 486 487 public RolapLevel getLevel() { 488 return level; 489 } 490 491 public List<RolapMember> getMembers() { 492 return members; 493 } 494 495 public boolean isPreferInterpreter(boolean joinArg) { 496 if (joinArg) { 497 // If this enumeration only contains calculated members, 498 // prefer non-native evaluation. 499 return hasCalcMembers && !hasNonCalcMembers; 500 } else { 501 // For non-join usage, always prefer non-native 502 // eval, since the members are already known. 503 return true; 504 } 505 } 506 507 public void addConstraint(SqlQuery sqlQuery, RolapCube baseCube) { 508 SqlConstraintUtils.addMemberConstraint( 509 sqlQuery, baseCube, null, 510 members, restrictMemberTypes, true); 511 } 512 513 /** 514 * Returns whether the input CJ arg is empty. 515 * 516 * <p>This is used to selectively push down empty input arg into the 517 * native evaluator. 518 * 519 * @return whether the input CJ arg is empty 520 */ 521 public boolean isEmptyCrossJoinArg() { 522 return (level == null && members.size() == 0); 523 } 524 525 public boolean hasCalcMembers() { 526 return hasCalcMembers; 527 } 528 529 public boolean hasAllMember() { 530 return hasAllMember; 531 } 532 533 public int hashCode() { 534 int c = 12; 535 for (RolapMember member : members) { 536 c = 31 * c + member.hashCode(); 537 } 538 if (restrictMemberTypes) { 539 c += 1; 540 } 541 return c; 542 } 543 544 public boolean equals(Object obj) { 545 if (!(obj instanceof MemberListCrossJoinArg)) { 546 return false; 547 } 548 MemberListCrossJoinArg that = (MemberListCrossJoinArg) obj; 549 if (this.restrictMemberTypes != that.restrictMemberTypes) { 550 return false; 551 } 552 for (int i = 0; i < members.size(); i++) { 553 if (this.members.get(i) != that.members.get(i)) { 554 return false; 555 } 556 } 557 return true; 558 } 559 } 560 561 /** 562 * Checks for Descendants(<member>, <Level>) 563 * 564 * @return an {@link CrossJoinArg} instance describing the Descendants 565 * function, or null if <code>fun</code> represents something else. 566 */ 567 protected CrossJoinArg checkDescendants( 568 Role role, 569 FunDef fun, 570 Exp[] args) 571 { 572 if (!"Descendants".equalsIgnoreCase(fun.getName())) { 573 return null; 574 } 575 if (args.length != 2) { 576 return null; 577 } 578 if (!(args[0] instanceof MemberExpr)) { 579 return null; 580 } 581 RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember(); 582 if (member.isCalculated()) { 583 return null; 584 } 585 if (!(args[1] instanceof LevelExpr)) { 586 return null; 587 } 588 RolapLevel level = (RolapLevel) ((LevelExpr) args[1]).getLevel(); 589 if (!isSimpleLevel(level)) { 590 return null; 591 } 592 // Descendants of a member in an access-controlled hierarchy cannot be 593 // converted to SQL. (We could be smarter; we don't currently notice 594 // when the member is in a part of the hierarchy that is not 595 // access-controlled.) 596 final Access access = role.getAccess(level.getHierarchy()); 597 switch (access) { 598 case ALL: 599 break; 600 default: 601 return null; 602 } 603 return new DescendantsCrossJoinArg(level, member); 604 } 605 606 /** 607 * Checks for <code><Level>.Members</code>. 608 * 609 * @return an {@link CrossJoinArg} instance describing the Level.members 610 * function, or null if <code>fun</code> represents something else. 611 */ 612 protected CrossJoinArg checkLevelMembers( 613 Role role, 614 FunDef fun, 615 Exp[] args) 616 { 617 if (!"Members".equalsIgnoreCase(fun.getName())) { 618 return null; 619 } 620 if (args.length != 1) { 621 return null; 622 } 623 if (!(args[0] instanceof LevelExpr)) { 624 return null; 625 } 626 RolapLevel level = (RolapLevel) ((LevelExpr) args[0]).getLevel(); 627 if (!isSimpleLevel(level)) { 628 return null; 629 } 630 // Members of a level in an access-controlled hierarchy cannot be 631 // converted to SQL. (We could be smarter; we don't currently notice 632 // when the level is in a part of the hierarchy that is not 633 // access-controlled.) 634 final Access access = role.getAccess(level.getHierarchy()); 635 switch (access) { 636 case ALL: 637 break; 638 default: 639 return null; 640 } 641 return new DescendantsCrossJoinArg(level, null); 642 } 643 644 /** 645 * Checks for <code><Member>.Children</code>. 646 * 647 * @return an {@link CrossJoinArg} instance describing the member.children 648 * function, or null if <code>fun</code> represents something else. 649 */ 650 protected CrossJoinArg checkMemberChildren( 651 Role role, 652 FunDef fun, 653 Exp[] args) 654 { 655 if (!"Children".equalsIgnoreCase(fun.getName())) { 656 return null; 657 } 658 if (args.length != 1) { 659 return null; 660 } 661 662 // Note: <Dimension>.Children is not recognized as a native expression. 663 if (!(args[0] instanceof MemberExpr)) { 664 return null; 665 } 666 RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember(); 667 if (member.isCalculated()) { 668 return null; 669 } 670 RolapLevel level = member.getLevel(); 671 level = (RolapLevel) level.getChildLevel(); 672 if (level == null || !isSimpleLevel(level)) { 673 // no child level 674 return null; 675 } 676 // Children of a member in an access-controlled hierarchy cannot be 677 // converted to SQL. (We could be smarter; we don't currently notice 678 // when the member is in a part of the hierarchy that is not 679 // access-controlled.) 680 final Access access = role.getAccess(level.getHierarchy()); 681 switch (access) { 682 case ALL: 683 break; 684 default: 685 return null; 686 } 687 return new DescendantsCrossJoinArg(level, member); 688 } 689 690 private static boolean isArgSizeSupported( 691 RolapEvaluator evaluator, 692 int argSize) { 693 boolean argSizeNotSupported = false; 694 695 // Note: srg size 0 is accepted as valid CJ argument 696 // This is used to push down the "1 = 0" predicate 697 // into the emerging CJ so that the entire CJ can 698 // be natively evaluated. 699 700 // First check that the member list will not result in a predicate 701 // longer than the underlying DB could support. 702 if (!evaluator.getDialect().supportsUnlimitedValueList() && 703 argSize > MondrianProperties.instance().MaxConstraints.get()) { 704 argSizeNotSupported = true; 705 } 706 707 return (!argSizeNotSupported); 708 } 709 710 /** 711 * Checks for a set constructor, <code>{member1, member2, 712 * ...}</code> that does not contain calculated members. 713 * 714 * @return an {@link CrossJoinArg} instance describing the enumeration, 715 * or null if <code>fun</code> represents something else. 716 */ 717 protected CrossJoinArg checkEnumeration(RolapEvaluator evaluator, 718 FunDef fun, Exp[] args) { 719 // Return null if not the expected funciton name or input size. 720 if (!"{}".equalsIgnoreCase(fun.getName()) || 721 !isArgSizeSupported(evaluator, args.length)) { 722 return null; 723 } 724 725 List<RolapMember> memberList = new ArrayList<RolapMember>(); 726 for (int i = 0; i < args.length; ++i) { 727 if (!(args[i] instanceof MemberExpr) || 728 ((MemberExpr) args[i]).getMember().isCalculated()) { 729 // also returns null if any member is calculated 730 return null; 731 } 732 memberList.add((RolapMember) 733 (((MemberExpr)args[i]).getMember())); 734 } 735 736 return MemberListCrossJoinArg.create(evaluator, memberList, restrictMemberTypes()); 737 } 738 739 /** 740 * Checks for <code>CrossJoin(<set1>, <set2>)</code>, where 741 * set1 and set2 are one of 742 * <code>member.children</code>, <code>level.members</code> or 743 * <code>member.descendants</code>. 744 * 745 * @param evaluator RolapEvaluator to use if inputs are to be evaluated 746 * @param fun the CrossJoin function, either "CrossJoin" or "NonEmptyCrossJoin". 747 * @param args inputs to the CrossJoin 748 * @return array of CrossJoinArg representing the inputs. 749 */ 750 protected CrossJoinArg[] checkCrossJoin( 751 RolapEvaluator evaluator, 752 FunDef fun, 753 Exp[] args) { 754 // is this "CrossJoin([A].children, [B].children)" 755 if (!"Crossjoin".equalsIgnoreCase(fun.getName()) && 756 !"NonEmptyCrossJoin".equalsIgnoreCase(fun.getName())) 757 { 758 return null; 759 } 760 if (args.length != 2) { 761 return null; 762 } 763 ExpCompiler compiler = evaluator.getQuery().createCompiler(); 764 765 // Check if the arguments can be natively evaluated. 766 // If not, try evaluating this argument and turning the result into 767 // MemberListCrossJoinArg. 768 // If either the inputs can be natively evaluated, or the result list 769 CrossJoinArg[] arg0 = checkCrossJoinArg(evaluator, args[0]); 770 if (arg0 == null) { 771 if (MondrianProperties.instance().ExpandNonNative.get()) { 772 ListCalc listCalc0 = compiler.compileList(args[0]); 773 List<RolapMember> list0 = Util.cast(listCalc0.evaluateList(evaluator)); 774 // Prevent the case when the second argument size is too large 775 if (list0 != null) { 776 Util.checkCJResultLimit(list0.size()); 777 } 778 CrossJoinArg arg = 779 MemberListCrossJoinArg.create(evaluator, list0, restrictMemberTypes()); 780 if (arg != null) { 781 arg0 = new CrossJoinArg[] {arg}; 782 } else { 783 return null; 784 } 785 } else { 786 return null; 787 } 788 } 789 790 CrossJoinArg[] arg1 = checkCrossJoinArg(evaluator, args[1]); 791 if (arg1 == null) { 792 if (MondrianProperties.instance().ExpandNonNative.get()) { 793 ListCalc listCalc1 = 794 (MemberListCalc) compiler.compileList(args[1]); 795 List<RolapMember> list1 = Util.cast(listCalc1.evaluateList(evaluator)); 796 // Prevent the case when the second argument size is too large 797 if (list1 != null) { 798 Util.checkCJResultLimit(list1.size()); 799 } 800 801 CrossJoinArg arg = 802 MemberListCrossJoinArg.create(evaluator, list1, restrictMemberTypes()); 803 if (arg != null) { 804 arg1 = new CrossJoinArg[] {arg}; 805 } else { 806 return null; 807 } 808 } else { 809 return null; 810 } 811 } 812 813 CrossJoinArg[] ret = new CrossJoinArg[arg0.length + arg1.length]; 814 System.arraycopy(arg0, 0, ret, 0, arg0.length); 815 System.arraycopy(arg1, 0, ret, arg0.length, arg1.length); 816 return ret; 817 } 818 819 /** 820 * Scans for memberChildren, levelMembers, memberDescendants, crossJoin. 821 */ 822 protected CrossJoinArg[] checkCrossJoinArg( 823 RolapEvaluator evaluator, 824 Exp exp) 825 { 826 if (exp instanceof NamedSetExpr) { 827 NamedSet namedSet = ((NamedSetExpr) exp).getNamedSet(); 828 exp = namedSet.getExp(); 829 } 830 if (!(exp instanceof ResolvedFunCall)) { 831 return null; 832 } 833 final ResolvedFunCall funCall = (ResolvedFunCall) exp; 834 FunDef fun = funCall.getFunDef(); 835 Exp[] args = funCall.getArgs(); 836 837 final Role role = evaluator.getSchemaReader().getRole(); 838 CrossJoinArg arg; 839 arg = checkMemberChildren(role, fun, args); 840 if (arg != null) { 841 return new CrossJoinArg[] {arg}; 842 } 843 arg = checkLevelMembers(role, fun, args); 844 if (arg != null) { 845 return new CrossJoinArg[] {arg}; 846 } 847 arg = checkDescendants(role, fun, args); 848 if (arg != null) { 849 return new CrossJoinArg[] {arg}; 850 } 851 arg = checkEnumeration(evaluator, fun, args); 852 if (arg != null) { 853 return new CrossJoinArg[] {arg}; 854 } 855 return checkCrossJoin(evaluator, fun, args); 856 } 857 858 /** 859 * Ensures that level is not ragged and not a parent/child level. 860 */ 861 protected static boolean isSimpleLevel(RolapLevel level) { 862 RolapHierarchy hier = level.getHierarchy(); 863 // does not work with ragged hierarchies 864 if (hier.isRagged()) { 865 return false; 866 } 867 // does not work with parent/child 868 if (level.isParentChild()) { 869 return false; 870 } 871 // does not work for measures 872 if (level.isMeasure()) { 873 return false; 874 } 875 return true; 876 } 877 878 /** 879 * Tests whether non-native evaluation is preferred for the 880 * given arguments. 881 * 882 * @param joinArg true if evaluating a cross-join; false if 883 * evaluating a single-input expression such as filter 884 * 885 * @return true if <em>all</em> args prefer the interpreter 886 */ 887 protected boolean isPreferInterpreter( 888 CrossJoinArg[] args, boolean joinArg) { 889 for (CrossJoinArg arg : args) { 890 if (!arg.isPreferInterpreter(joinArg)) { 891 return false; 892 } 893 } 894 return true; 895 } 896 897 /** disable garbage collection for test */ 898 void useHardCache(boolean hard) { 899 if (hard) { 900 cache = new HardSmartCache(); 901 } else { 902 cache = new SoftSmartCache(); 903 } 904 } 905 906 /** 907 * Override current members in position by default members in 908 * hierarchies which are involved in this filter/topcount. 909 * Stores the RolapStoredMeasure into the context because that is needed to 910 * generate a cell request to constraint the sql. 911 * 912 * The current context may contain a calculated measure, this measure 913 * was translated into an sql condition (filter/topcount). The measure 914 * is not used to constrain the result but only to access the star. 915 * 916 * @see RolapAggregationManager#makeRequest(RolapEvaluator) 917 */ 918 protected RolapEvaluator overrideContext( 919 RolapEvaluator evaluator, 920 CrossJoinArg[] cargs, 921 RolapStoredMeasure storedMeasure) 922 { 923 SchemaReader schemaReader = evaluator.getSchemaReader(); 924 RolapEvaluator newEvaluator = (RolapEvaluator) evaluator.push(); 925 for (CrossJoinArg carg : cargs) { 926 RolapLevel level = carg.getLevel(); 927 if (level != null) { 928 Hierarchy hierarchy = level.getHierarchy(); 929 Member defaultMember = 930 schemaReader.getHierarchyDefaultMember(hierarchy); 931 newEvaluator.setContext(defaultMember); 932 } 933 } 934 if (storedMeasure != null) 935 newEvaluator.setContext(storedMeasure); 936 return newEvaluator; 937 } 938 939 940 public interface SchemaReaderWithMemberReaderAvailable extends SchemaReader { 941 MemberReader getMemberReader(Hierarchy hierarchy); 942 } 943 944 private static class SchemaReaderWithMemberReaderCache 945 extends DelegatingSchemaReader 946 implements SchemaReaderWithMemberReaderAvailable { 947 private final Map<Hierarchy,MemberReader> hierarchyReaders = 948 new HashMap<Hierarchy, MemberReader>(); 949 950 SchemaReaderWithMemberReaderCache(SchemaReader schemaReader) { 951 super(schemaReader); 952 } 953 954 public synchronized MemberReader getMemberReader(Hierarchy hierarchy) { 955 MemberReader memberReader = hierarchyReaders.get(hierarchy); 956 if (memberReader == null) { 957 memberReader = 958 ((RolapHierarchy) hierarchy).createMemberReader( 959 schemaReader.getRole()); 960 hierarchyReaders.put(hierarchy, memberReader); 961 } 962 return memberReader; 963 } 964 } 965 } 966 967 // End RolapNativeSet.java