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-2011 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap.sql; 012 013import mondrian.calc.*; 014import mondrian.mdx.*; 015import mondrian.olap.*; 016import mondrian.olap.Role.RollupPolicy; 017import mondrian.olap.fun.*; 018import mondrian.olap.type.HierarchyType; 019import mondrian.olap.type.Type; 020import mondrian.rolap.*; 021 022import org.apache.log4j.Logger; 023 024import java.util.*; 025 026/** 027 * Creates CrossJoinArgs for use in constraining SQL queries. 028 * 029 * @author kwalker 030 * @since Dec 15, 2009 031 */ 032public class CrossJoinArgFactory { 033 protected static final Logger LOGGER = 034 Logger.getLogger(CrossJoinArgFactory.class); 035 private boolean restrictMemberTypes; 036 037 public CrossJoinArgFactory(boolean restrictMemberTypes) { 038 this.restrictMemberTypes = restrictMemberTypes; 039 } 040 041 public Set<CrossJoinArg> buildConstraintFromAllAxes( 042 final RolapEvaluator evaluator) 043 { 044 Set<CrossJoinArg> joinArgs = 045 new LinkedHashSet<CrossJoinArg>(); 046 for (QueryAxis ax : evaluator.getQuery().getAxes()) { 047 List<CrossJoinArg[]> axesArgs = 048 checkCrossJoinArg(evaluator, ax.getSet(), true); 049 if (axesArgs != null) { 050 for (CrossJoinArg[] axesArg : axesArgs) { 051 joinArgs.addAll(Arrays.asList(axesArg)); 052 } 053 } 054 } 055 return joinArgs; 056 } 057 058 /** 059 * Scans for memberChildren, levelMembers, memberDescendants, crossJoin. 060 */ 061 public List<CrossJoinArg[]> checkCrossJoinArg( 062 RolapEvaluator evaluator, 063 Exp exp) 064 { 065 return checkCrossJoinArg(evaluator, exp, false); 066 } 067 068 /** 069 * Checks whether an expression can be natively evaluated. The following 070 * expressions can be natively evaluated: 071 * <p/> 072 * <ul> 073 * <li>member.Children 074 * <li>level.members 075 * <li>descendents of a member 076 * <li>member list 077 * <li>filter on a dimension 078 * </ul> 079 * 080 * @param evaluator Evaluator 081 * @param exp Expresssion 082 * @return List of CrossJoinArg arrays. The first array represent the 083 * CJ CrossJoinArg and the second array represent the additional 084 * constraints. 085 */ 086 List<CrossJoinArg[]> checkCrossJoinArg( 087 RolapEvaluator evaluator, 088 Exp exp, 089 final boolean returnAny) 090 { 091 if (exp instanceof NamedSetExpr) { 092 NamedSet namedSet = ((NamedSetExpr) exp).getNamedSet(); 093 exp = namedSet.getExp(); 094 } 095 if (!(exp instanceof ResolvedFunCall)) { 096 return null; 097 } 098 final ResolvedFunCall funCall = (ResolvedFunCall) exp; 099 FunDef fun = funCall.getFunDef(); 100 Exp[] args = funCall.getArgs(); 101 102 final Role role = evaluator.getSchemaReader().getRole(); 103 CrossJoinArg[] cjArgs; 104 105 cjArgs = checkMemberChildren(role, fun, args); 106 if (cjArgs != null) { 107 return Collections.singletonList(cjArgs); 108 } 109 cjArgs = checkLevelMembers(role, fun, args); 110 if (cjArgs != null) { 111 return Collections.singletonList(cjArgs); 112 } 113 cjArgs = checkDescendants(role, fun, args); 114 if (cjArgs != null) { 115 return Collections.singletonList(cjArgs); 116 } 117 final boolean exclude = false; 118 cjArgs = checkEnumeration(evaluator, fun, args, exclude); 119 if (cjArgs != null) { 120 return Collections.singletonList(cjArgs); 121 } 122 123 if (returnAny) { 124 cjArgs = checkConstrainedMeasures(evaluator, fun, args); 125 if (cjArgs != null) { 126 return Collections.singletonList(cjArgs); 127 } 128 } 129 130 List<CrossJoinArg[]> allArgs = 131 checkDimensionFilter(evaluator, fun, args); 132 if (allArgs != null) { 133 return allArgs; 134 } 135 // strip off redundant set braces, for example 136 // { Gender.Gender.members }, or {{{ Gender.M }}} 137 if ("{}".equalsIgnoreCase(fun.getName()) && args.length == 1) { 138 return checkCrossJoinArg(evaluator, args[0], returnAny); 139 } 140 if ("NativizeSet".equalsIgnoreCase(fun.getName()) && args.length == 1) { 141 return checkCrossJoinArg(evaluator, args[0], returnAny); 142 } 143 return checkCrossJoin(evaluator, fun, args, returnAny); 144 } 145 146 private CrossJoinArg[] checkConstrainedMeasures( 147 RolapEvaluator evaluator, FunDef fun, Exp[] args) 148 { 149 if (isSetOfConstrainedMeasures(fun, args)) { 150 HashMap<Dimension, List<RolapMember>> memberLists = 151 new LinkedHashMap<Dimension, List<RolapMember>>(); 152 for (Exp arg : args) { 153 addConstrainingMembersToMap(arg, memberLists); 154 } 155 return memberListCrossJoinArgArray(memberLists, args, evaluator); 156 } 157 return null; 158 } 159 160 private boolean isSetOfConstrainedMeasures(FunDef fun, Exp[] args) { 161 return fun.getName().equals("{}") && allArgsConstrainedMeasure(args); 162 } 163 164 private boolean allArgsConstrainedMeasure(Exp[] args) { 165 for (Exp arg : args) { 166 if (!isConstrainedMeasure(arg)) { 167 return false; 168 } 169 } 170 return true; 171 } 172 173 private boolean isConstrainedMeasure(Exp arg) { 174 if (!(arg instanceof MemberExpr 175 && ((MemberExpr) arg).getMember().isMeasure())) 176 { 177 if (arg instanceof ResolvedFunCall) { 178 ResolvedFunCall call = (ResolvedFunCall) arg; 179 if (call.getFunDef() instanceof SetFunDef 180 || call.getFunDef() instanceof ParenthesesFunDef) 181 { 182 return allArgsConstrainedMeasure(call.getArgs()); 183 } 184 } 185 return false; 186 } 187 Member member = ((MemberExpr) arg).getMember(); 188 if (member instanceof RolapCalculatedMember) { 189 Exp calcExp = 190 ((RolapCalculatedMember) member).getFormula().getExpression(); 191 return ((calcExp instanceof ResolvedFunCall 192 && ((ResolvedFunCall) calcExp).getFunDef() 193 instanceof TupleFunDef)) 194 || calcExp instanceof Literal; 195 } 196 return false; 197 } 198 199 private void addConstrainingMembersToMap( 200 Exp arg, Map<Dimension, List<RolapMember>> memberLists) 201 { 202 if (arg instanceof ResolvedFunCall) { 203 ResolvedFunCall call = (ResolvedFunCall) arg; 204 for (Exp callArg : call.getArgs()) { 205 addConstrainingMembersToMap(callArg, memberLists); 206 } 207 } 208 Exp[] tupleArgs = getCalculatedTupleArgs(arg); 209 for (Exp tupleArg : tupleArgs) { 210 Dimension dimension = tupleArg.getType().getDimension(); 211 if (!dimension.isMeasures()) { 212 List<RolapMember> members; 213 if (memberLists.containsKey(dimension)) { 214 members = memberLists.get(dimension); 215 } else { 216 members = new ArrayList<RolapMember>(); 217 } 218 members.add((RolapMember) ((MemberExpr) tupleArg).getMember()); 219 memberLists.put(dimension, members); 220 } else if (isConstrainedMeasure(tupleArg)) { 221 addConstrainingMembersToMap(tupleArg, memberLists); 222 } 223 } 224 } 225 226 private Exp[] getCalculatedTupleArgs(Exp arg) { 227 if (arg instanceof MemberExpr) { 228 Member member = ((MemberExpr) arg).getMember(); 229 if (member instanceof RolapCalculatedMember) { 230 Exp formulaExp = 231 ((RolapCalculatedMember) member) 232 .getFormula().getExpression(); 233 if (formulaExp instanceof ResolvedFunCall) { 234 return ((ResolvedFunCall) formulaExp).getArgs(); 235 } 236 } 237 } 238 return new Exp[0]; 239 } 240 241 private CrossJoinArg[] memberListCrossJoinArgArray( 242 Map<Dimension, List<RolapMember>> memberLists, 243 Exp[] args, 244 RolapEvaluator evaluator) 245 { 246 List<CrossJoinArg> argList = new ArrayList<CrossJoinArg>(); 247 for (List<RolapMember> memberList : memberLists.values()) { 248 if (memberList.size() == countNonLiteralMeasures(args)) { 249 //when the memberList and args list have the same length 250 //it means there must have been a constraint on each measure 251 //for this dimension. 252 final CrossJoinArg cjArg = 253 MemberListCrossJoinArg.create( 254 evaluator, 255 removeDuplicates(memberList), 256 restrictMemberTypes(), false); 257 if (cjArg != null) { 258 argList.add(cjArg); 259 } 260 } 261 } 262 if (argList.size() > 0) { 263 return argList.toArray(new CrossJoinArg[argList.size()]); 264 } 265 return null; 266 } 267 268 private List<RolapMember> removeDuplicates(List<RolapMember> list) 269 { 270 Set<RolapMember> set = new HashSet<RolapMember>(); 271 List<RolapMember> uniqueList = new ArrayList<RolapMember>(); 272 for (RolapMember element : list) { 273 if (set.add(element)) { 274 uniqueList.add(element); 275 } 276 } 277 return uniqueList; 278 } 279 280 private int countNonLiteralMeasures(Exp[] length) { 281 int count = 0; 282 for (Exp exp : length) { 283 if (exp instanceof MemberExpr) { 284 Exp calcExp = ((MemberExpr) exp).getMember().getExpression(); 285 if (!(calcExp instanceof Literal)) { 286 count++; 287 } 288 } else if (exp instanceof ResolvedFunCall) { 289 count += 290 countNonLiteralMeasures(((ResolvedFunCall) exp).getArgs()); 291 } 292 } 293 return count; 294 } 295 296 /** 297 * Checks for <code>CrossJoin(<set1>, <set2>)</code>, where 298 * set1 and set2 are one of 299 * <code>member.children</code>, <code>level.members</code> or 300 * <code>member.descendants</code>. 301 * 302 * @param evaluator Evaluator to use if inputs are to be evaluated 303 * @param fun The function, either "CrossJoin" or "NonEmptyCrossJoin" 304 * @param args Inputs to the CrossJoin 305 * @param returnAny indicates we should return any valid crossjoin args 306 * @return array of CrossJoinArg representing the inputs 307 */ 308 public List<CrossJoinArg[]> checkCrossJoin( 309 RolapEvaluator evaluator, 310 FunDef fun, 311 Exp[] args, 312 final boolean returnAny) 313 { 314 // is this "CrossJoin([A].children, [B].children)" 315 if (!"Crossjoin".equalsIgnoreCase(fun.getName()) 316 && !"NonEmptyCrossJoin".equalsIgnoreCase(fun.getName())) 317 { 318 return null; 319 } 320 if (args.length != 2) { 321 return null; 322 } 323 // Check if the arguments can be natively evaluated. 324 // If not, try evaluating this argument and turning the result into 325 // MemberListCrossJoinArg. 326 List<CrossJoinArg[]> allArgsOneInput; 327 // An array(size 2) of arrays(size arbitary). Each outer array represent 328 // native inputs fro one input. 329 CrossJoinArg[][] cjArgsBothInputs = 330 new CrossJoinArg[2][]; 331 CrossJoinArg[][] predicateArgsBothInputs = 332 new CrossJoinArg[2][]; 333 334 for (int i = 0; i < 2; i++) { 335 allArgsOneInput = checkCrossJoinArg(evaluator, args[i], returnAny); 336 337 if (allArgsOneInput == null 338 || allArgsOneInput.isEmpty() 339 || allArgsOneInput.get(0) == null) 340 { 341 cjArgsBothInputs[i] = expandNonNative(evaluator, args[i]); 342 } else { 343 // Collect CJ CrossJoinArg 344 cjArgsBothInputs[i] = allArgsOneInput.get(0); 345 } 346 if (returnAny) { 347 continue; 348 } 349 if (cjArgsBothInputs[i] == null) { 350 return null; 351 } 352 353 // Collect Predicate CrossJoinArg if it exists. 354 predicateArgsBothInputs[i] = null; 355 if (allArgsOneInput != null && allArgsOneInput.size() == 2) { 356 predicateArgsBothInputs[i] = allArgsOneInput.get(1); 357 } 358 } 359 360 List<CrossJoinArg[]> allArgsBothInputs = 361 new ArrayList<CrossJoinArg[]>(); 362 // Now combine the cjArgs from both sides 363 CrossJoinArg[] combinedCJArgs = 364 Util.appendArrays( 365 cjArgsBothInputs[0] == null 366 ? CrossJoinArg.EMPTY_ARRAY 367 : cjArgsBothInputs[0], 368 cjArgsBothInputs[1] == null 369 ? CrossJoinArg.EMPTY_ARRAY 370 : cjArgsBothInputs[1]); 371 allArgsBothInputs.add(combinedCJArgs); 372 373 CrossJoinArg[] combinedPredicateArgs = 374 Util.appendArrays( 375 predicateArgsBothInputs[0] == null 376 ? CrossJoinArg.EMPTY_ARRAY 377 : predicateArgsBothInputs[0], 378 predicateArgsBothInputs[1] == null 379 ? CrossJoinArg.EMPTY_ARRAY 380 : predicateArgsBothInputs[1]); 381 if (combinedPredicateArgs.length > 0) { 382 allArgsBothInputs.add(combinedPredicateArgs); 383 } 384 385 return allArgsBothInputs; 386 } 387 388 /** 389 * Checks for a set constructor, <code>{member1, member2, 390 * ...}</code> that does not contain calculated members. 391 * 392 * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the enumeration, 393 * or null if <code>fun</code> represents something else. 394 */ 395 private CrossJoinArg[] checkEnumeration( 396 RolapEvaluator evaluator, 397 FunDef fun, 398 Exp[] args, 399 boolean exclude) 400 { 401 // Return null if not the expected function name or input size. 402 if (fun == null) { 403 if (args.length != 1) { 404 return null; 405 } 406 } else { 407 if (!"{}".equalsIgnoreCase(fun.getName()) 408 || !isArgSizeSupported(evaluator, args.length)) 409 { 410 return null; 411 } 412 } 413 414 List<RolapMember> memberList = new ArrayList<RolapMember>(); 415 for (Exp arg : args) { 416 if (!(arg instanceof MemberExpr)) { 417 return null; 418 } 419 final Member member = ((MemberExpr) arg).getMember(); 420 if (member.isCalculated() 421 && !member.isParentChildLeaf()) 422 { 423 // also returns null if any member is calculated 424 return null; 425 } 426 memberList.add((RolapMember) member); 427 } 428 429 final CrossJoinArg cjArg = 430 MemberListCrossJoinArg.create( 431 evaluator, memberList, restrictMemberTypes(), exclude); 432 if (cjArg == null) { 433 return null; 434 } 435 return new CrossJoinArg[]{cjArg}; 436 } 437 438 private boolean restrictMemberTypes() { 439 return restrictMemberTypes; 440 } 441 442 /** 443 * Checks for <code><Member>.Children</code>. 444 * 445 * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the member.children 446 * function, or null if <code>fun</code> represents something else. 447 */ 448 private CrossJoinArg[] checkMemberChildren( 449 Role role, 450 FunDef fun, 451 Exp[] args) 452 { 453 if (!"Children".equalsIgnoreCase(fun.getName())) { 454 return null; 455 } 456 if (args.length != 1) { 457 return null; 458 } 459 460 // Note: <Dimension>.Children is not recognized as a native expression. 461 if (!(args[0] instanceof MemberExpr)) { 462 return null; 463 } 464 RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember(); 465 if (member.isCalculated()) { 466 return null; 467 } 468 RolapLevel level = member.getLevel(); 469 level = (RolapLevel) level.getChildLevel(); 470 if (level == null || !level.isSimple()) { 471 // no child level 472 return null; 473 } 474 // Children of a member in an access-controlled hierarchy cannot be 475 // converted to SQL when RollupPolicy=FULL. (We could be smarter; we 476 // don't currently notice when we don't look below the rolled up level 477 // therefore no access-control is needed. 478 final Access access = role.getAccess(level.getHierarchy()); 479 switch (access) { 480 case ALL: 481 break; 482 case CUSTOM: 483 final RollupPolicy rollupPolicy = 484 role.getAccessDetails(level.getHierarchy()).getRollupPolicy(); 485 if (rollupPolicy == RollupPolicy.FULL) { 486 return null; 487 } 488 break; 489 default: 490 return null; 491 } 492 return new CrossJoinArg[]{ 493 new DescendantsCrossJoinArg(level, member) 494 }; 495 } 496 497 /** 498 * Checks for <code><Level>.Members</code>. 499 * 500 * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the Level.members 501 * function, or null if <code>fun</code> represents something else. 502 */ 503 private CrossJoinArg[] checkLevelMembers( 504 Role role, 505 FunDef fun, 506 Exp[] args) 507 { 508 if (!"Members".equalsIgnoreCase(fun.getName())) { 509 return null; 510 } 511 if (args.length != 1) { 512 return null; 513 } 514 if (!(args[0] instanceof LevelExpr)) { 515 return null; 516 } 517 RolapLevel level = (RolapLevel) ((LevelExpr) args[0]).getLevel(); 518 if (!level.isSimple()) { 519 return null; 520 } 521 // Members of a level in an access-controlled hierarchy cannot be 522 // converted to SQL when RollupPolicy=FULL. (We could be smarter; we 523 // don't currently notice when we don't look below the rolled up level 524 // therefore no access-control is needed. 525 final Access access = role.getAccess(level.getHierarchy()); 526 switch (access) { 527 case ALL: 528 break; 529 case CUSTOM: 530 final RollupPolicy rollupPolicy = 531 role.getAccessDetails(level.getHierarchy()).getRollupPolicy(); 532 if (rollupPolicy == RollupPolicy.FULL) { 533 return null; 534 } 535 break; 536 default: 537 return null; 538 } 539 return new CrossJoinArg[]{ 540 new DescendantsCrossJoinArg(level, null) 541 }; 542 } 543 544 545 private static boolean isArgSizeSupported( 546 RolapEvaluator evaluator, 547 int argSize) 548 { 549 boolean argSizeNotSupported = false; 550 551 // Note: arg size 0 is accepted as valid CJ argument 552 // This is used to push down the "1 = 0" predicate 553 // into the emerging CJ so that the entire CJ can 554 // be natively evaluated. 555 556 // First check that the member list will not result in a predicate 557 // longer than the underlying DB could support. 558 if (argSize > MondrianProperties.instance().MaxConstraints.get()) { 559 argSizeNotSupported = true; 560 } 561 562 return !argSizeNotSupported; 563 } 564 565 /** 566 * Checks for Descendants(<member>, <Level>) 567 * 568 * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the Descendants 569 * function, or null if <code>fun</code> represents something else. 570 */ 571 private CrossJoinArg[] checkDescendants( 572 Role role, 573 FunDef fun, 574 Exp[] args) 575 { 576 if (!"Descendants".equalsIgnoreCase(fun.getName())) { 577 return null; 578 } 579 if (args.length != 2) { 580 return null; 581 } 582 if (!(args[0] instanceof MemberExpr)) { 583 return null; 584 } 585 RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember(); 586 if (member.isCalculated()) { 587 return null; 588 } 589 RolapLevel level = null; 590 if ((args[1] instanceof LevelExpr)) { 591 level = (RolapLevel) ((LevelExpr) args[1]).getLevel(); 592 } else if (args[1] instanceof Literal) { 593 RolapLevel[] levels = (RolapLevel[]) 594 member.getHierarchy().getLevels(); 595 int currentDepth = member.getDepth(); 596 Literal descendantsDepth = (Literal) args[1]; 597 int newDepth = currentDepth + descendantsDepth.getIntValue(); 598 if (newDepth < levels.length) { 599 level = levels[newDepth]; 600 } 601 } else { 602 return null; 603 } 604 605 if (!level.isSimple()) { 606 return null; 607 } 608 // Descendants of a member in an access-controlled hierarchy cannot be 609 // converted to SQL. (We could be smarter; we don't currently notice 610 // when the member is in a part of the hierarchy that is not 611 // access-controlled.) 612 final Access access = role.getAccess(level.getHierarchy()); 613 switch (access) { 614 case ALL: 615 break; 616 default: 617 return null; 618 } 619 return new CrossJoinArg[]{ 620 new DescendantsCrossJoinArg(level, member) 621 }; 622 } 623 624 /** 625 * Check if a dimension filter can be natively evaluated. 626 * Currently, these types of filters can be natively evaluated: 627 * Filter(Set, Qualified Predicate) 628 * where Qualified Predicate is either 629 * CurrentMember reference IN {m1, m2}, 630 * CurrentMember reference Is m1, 631 * negation(NOT) of qualified predicate 632 * conjuction(AND) of qualified predicates 633 * and where 634 * currentmember reference is either a member or 635 * ancester of a member from the context, 636 * 637 * @param evaluator Evaluator 638 * @param fun Filter function 639 * @param filterArgs inputs to the Filter function 640 * @return a list of CrossJoinArg arrays. The first array is the CrossJoin 641 * dimensions. The second array, if any, contains additional 642 * constraints on the dimensions. If either the list or the first 643 * array is null, then native cross join is not feasible. 644 */ 645 private List<CrossJoinArg[]> checkDimensionFilter( 646 RolapEvaluator evaluator, 647 FunDef fun, 648 Exp[] filterArgs) 649 { 650 if (!MondrianProperties.instance().EnableNativeFilter.get()) { 651 return null; 652 } 653 654 // Return null if not the expected funciton name or input size. 655 if (!"Filter".equalsIgnoreCase(fun.getName()) 656 || filterArgs.length != 2) 657 { 658 return null; 659 } 660 661 // Now check filterArg[0] can be natively evaluated. 662 // checkCrossJoin returns a list of CrossJoinArg arrays. 663 // The first array is the CrossJoin dimensions 664 // The second array, if any, contains additional constraints on the 665 // dimensions. If either the list or the first array is null, then 666 // native cross join is not feasible. 667 List<CrossJoinArg[]> allArgs = 668 checkCrossJoinArg(evaluator, filterArgs[0]); 669 670 if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) { 671 return null; 672 } 673 674 final CrossJoinArg[] cjArgs = allArgs.get(0); 675 if (cjArgs == null) { 676 return null; 677 } 678 679 final CrossJoinArg[] previousPredicateArgs; 680 if (allArgs.size() == 2) { 681 previousPredicateArgs = allArgs.get(1); 682 } else { 683 previousPredicateArgs = null; 684 } 685 686 // True if the Filter wants to exclude member(s) 687 final boolean exclude = false; 688 689 // Check that filterArgs[1] is a qualified predicate 690 // Composites such as AND/OR are not supported at this time 691 CrossJoinArg[] currentPredicateArgs; 692 if (filterArgs[1] instanceof ResolvedFunCall) { 693 ResolvedFunCall predicateCall = (ResolvedFunCall) filterArgs[1]; 694 695 currentPredicateArgs = 696 checkFilterPredicate(evaluator, predicateCall, exclude); 697 } else { 698 currentPredicateArgs = null; 699 } 700 701 if (currentPredicateArgs == null) { 702 return null; 703 } 704 705 // cjArgs remain the same but now there is more predicateArgs 706 // Combine the previous predicate args with the current predicate args. 707 LOGGER.debug("using native dimension filter"); 708 CrossJoinArg[] combinedPredicateArgs = 709 currentPredicateArgs; 710 711 if (previousPredicateArgs != null) { 712 combinedPredicateArgs = 713 Util.appendArrays(previousPredicateArgs, currentPredicateArgs); 714 } 715 716 // CJ args do not change. 717 // Predicate args will grow if filter is native. 718 return Arrays.asList(cjArgs, combinedPredicateArgs); 719 } 720 721 /** 722 * Checks whether the filter predicate can be turned into native SQL. 723 * See comment for checkDimensionFilter for the types of predicates 724 * suported. 725 * 726 * @param evaluator Evaluator 727 * @param predicateCall Call to predicate function (ANd, NOT or parentheses) 728 * @param exclude Whether to exclude tuples that match the predicate 729 * @return if filter predicate can be natively evaluated, the CrossJoinArg 730 * array representing the predicate; otherwise, null. 731 */ 732 private CrossJoinArg[] checkFilterPredicate( 733 RolapEvaluator evaluator, 734 ResolvedFunCall predicateCall, 735 boolean exclude) 736 { 737 CrossJoinArg[] predicateCJArgs = null; 738 if (predicateCall.getFunName().equals("()")) { 739 Exp actualPredicateCall = predicateCall.getArg(0); 740 if (actualPredicateCall instanceof ResolvedFunCall) { 741 return checkFilterPredicate( 742 evaluator, (ResolvedFunCall) actualPredicateCall, exclude); 743 } else { 744 return null; 745 } 746 } 747 748 if (predicateCall.getFunName().equals("NOT") 749 && predicateCall.getArg(0) instanceof ResolvedFunCall) 750 { 751 predicateCall = (ResolvedFunCall) predicateCall.getArg(0); 752 // Flip the exclude flag 753 exclude = !exclude; 754 return checkFilterPredicate(evaluator, predicateCall, exclude); 755 } 756 757 if (predicateCall.getFunName().equals("AND")) { 758 Exp andArg0 = predicateCall.getArg(0); 759 Exp andArg1 = predicateCall.getArg(1); 760 761 if (andArg0 instanceof ResolvedFunCall 762 && andArg1 instanceof ResolvedFunCall) 763 { 764 CrossJoinArg[] andCJArgs0; 765 CrossJoinArg[] andCJArgs1; 766 andCJArgs0 = 767 checkFilterPredicate( 768 evaluator, (ResolvedFunCall) andArg0, exclude); 769 if (andCJArgs0 != null) { 770 andCJArgs1 = 771 checkFilterPredicate( 772 evaluator, (ResolvedFunCall) andArg1, exclude); 773 if (andCJArgs1 != null) { 774 predicateCJArgs = 775 Util.appendArrays(andCJArgs0, andCJArgs1); 776 } 777 } 778 } 779 // predicateCJArgs is either initialized or null 780 return predicateCJArgs; 781 } 782 783 // Now check the broken down predicate clause. 784 predicateCJArgs = 785 checkFilterPredicateInIs(evaluator, predicateCall, exclude); 786 return predicateCJArgs; 787 } 788 789 /** 790 * Check whether the predicate is an IN or IS predicate and can be 791 * natively evaluated. 792 * 793 * @param evaluator 794 * @param predicateCall 795 * @param exclude 796 * @return the array of CrossJoinArg containing the predicate. 797 */ 798 private CrossJoinArg[] checkFilterPredicateInIs( 799 RolapEvaluator evaluator, 800 ResolvedFunCall predicateCall, 801 boolean exclude) 802 { 803 final boolean useIs; 804 if (predicateCall.getFunName().equals("IS")) { 805 useIs = true; 806 } else if (predicateCall.getFunName().equals("IN")) { 807 useIs = false; 808 } else { 809 // Neither IN nor IS 810 // This predicate can not be natively evaluated. 811 return null; 812 } 813 814 Exp[] predArgs = predicateCall.getArgs(); 815 if (predArgs.length != 2) { 816 return null; 817 } 818 819 // Check that predArgs[0] is a ResolvedFuncCall while FunDef is: 820 // DimensionCurrentMemberFunDef 821 // HierarchyCurrentMemberFunDef 822 // or Ancestor of those functions. 823 if (!(predArgs[0] instanceof ResolvedFunCall)) { 824 return null; 825 } 826 827 ResolvedFunCall predFirstArgCall = (ResolvedFunCall) predArgs[0]; 828 if (predFirstArgCall.getFunDef().getName().equals("Ancestor")) { 829 Exp[] ancestorArgs = predFirstArgCall.getArgs(); 830 831 if (!(ancestorArgs[0] instanceof ResolvedFunCall)) { 832 return null; 833 } 834 835 predFirstArgCall = (ResolvedFunCall) ancestorArgs[0]; 836 } 837 838 // Now check that predFirstArgCall is a CurrentMember function that 839 // refers to the dimension being filtered 840 FunDef predFirstArgFun = predFirstArgCall.getFunDef(); 841 if (!predFirstArgFun.getName().equals("CurrentMember")) { 842 return null; 843 } 844 845 Exp currentMemberArg = predFirstArgCall.getArg(0); 846 Type currentMemberArgType = currentMemberArg.getType(); 847 848 // Input to CurremntMember should be either Dimension or Hierarchy type. 849 if (!(currentMemberArgType 850 instanceof mondrian.olap.type.DimensionType 851 || currentMemberArgType instanceof HierarchyType)) 852 { 853 return null; 854 } 855 856 // It is not necessary to check currentMemberArg comes from the same 857 // dimension as one of the filterCJArgs, because query parser makes sure 858 // that currentMember always references dimensions in context. 859 860 // Check that predArgs[1] can be expressed as an MemberListCrossJoinArg. 861 Exp predSecondArg = predArgs[1]; 862 Exp[] predSecondArgList; 863 FunDef predSecondArgFun; 864 CrossJoinArg[] predCJArgs; 865 866 if (useIs) { 867 // IS operator 868 if (!(predSecondArg instanceof MemberExpr)) { 869 return null; 870 } 871 872 // IS predicate only contains one member 873 // Make it into a list to be uniform with IN predicate. 874 predSecondArgFun = null; 875 predSecondArgList = new Exp[]{predSecondArg}; 876 } else { 877 // IN operator 878 if (predSecondArg instanceof NamedSetExpr) { 879 NamedSet namedSet = 880 ((NamedSetExpr) predSecondArg).getNamedSet(); 881 predSecondArg = namedSet.getExp(); 882 } 883 884 if (!(predSecondArg instanceof ResolvedFunCall)) { 885 return null; 886 } 887 888 ResolvedFunCall predSecondArgCall = 889 (ResolvedFunCall) predSecondArg; 890 predSecondArgFun = predSecondArgCall.getFunDef(); 891 predSecondArgList = predSecondArgCall.getArgs(); 892 } 893 894 predCJArgs = 895 checkEnumeration( 896 evaluator, predSecondArgFun, predSecondArgList, exclude); 897 return predCJArgs; 898 } 899 900 private CrossJoinArg[] expandNonNative( 901 RolapEvaluator evaluator, 902 Exp exp) 903 { 904 ExpCompiler compiler = evaluator.getQuery().createCompiler(); 905 CrossJoinArg[] arg0 = null; 906 if (shouldExpandNonEmpty(exp) 907 && evaluator.getActiveNativeExpansions().add(exp)) 908 { 909 ListCalc listCalc0 = compiler.compileList(exp); 910 final TupleList tupleList = listCalc0.evaluateList(evaluator); 911 912 // Prevent the case when the second argument size is too large 913 Util.checkCJResultLimit(tupleList.size()); 914 915 if (tupleList.getArity() == 1) { 916 List<RolapMember> list0 = 917 Util.cast(tupleList.slice(0)); 918 CrossJoinArg arg = 919 MemberListCrossJoinArg.create( 920 evaluator, list0, restrictMemberTypes(), false); 921 if (arg != null) { 922 arg0 = new CrossJoinArg[]{arg}; 923 } 924 } 925 evaluator.getActiveNativeExpansions().remove(exp); 926 } 927 return arg0; 928 } 929 930 private boolean shouldExpandNonEmpty(Exp exp) { 931 return MondrianProperties.instance().ExpandNonNative.get() 932// && !MondrianProperties.instance().EnableNativeCrossJoin.get() 933 || isCheapSet(exp); 934 } 935 936 private boolean isCheapSet(Exp exp) { 937 return isSet(exp) && allArgsCheapToExpand(exp); 938 } 939 940 private static final List<String> cheapFuns = 941 Arrays.asList("LastChild", "FirstChild", "Lag"); 942 943 private boolean allArgsCheapToExpand(Exp exp) { 944 while (exp instanceof NamedSetExpr) { 945 exp = ((NamedSetExpr) exp).getNamedSet().getExp(); 946 } 947 for (Exp arg : ((ResolvedFunCall) exp).getArgs()) { 948 if (arg instanceof ResolvedFunCall) { 949 if (!cheapFuns.contains(((ResolvedFunCall) arg).getFunName())) { 950 return false; 951 } 952 } else if (!(arg instanceof MemberExpr)) { 953 return false; 954 } 955 } 956 return true; 957 } 958 959 private boolean isSet(Exp exp) { 960 return ((exp instanceof ResolvedFunCall) 961 && ((ResolvedFunCall) exp).getFunName().equals("{}")) 962 || (exp instanceof NamedSetExpr); 963 } 964} 965 966// End CrossJoinArgFactory.java