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) 2002-2005 Julian Hyde 008// Copyright (C) 2005-2011 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.olap.fun; 012 013import mondrian.calc.*; 014import mondrian.calc.impl.*; 015import mondrian.mdx.*; 016import mondrian.olap.*; 017import mondrian.olap.type.*; 018import mondrian.rolap.RolapEvaluator; 019import mondrian.rolap.RolapUtil; 020import mondrian.util.CartesianProductList; 021 022import java.util.*; 023 024/** 025 * Definition of the <code>CrossJoin</code> MDX function. 026 * 027 * @author jhyde 028 * @since Mar 23, 2006 029 */ 030public class CrossJoinFunDef extends FunDefBase { 031 static final ReflectiveMultiResolver Resolver = 032 new ReflectiveMultiResolver( 033 "Crossjoin", 034 "Crossjoin(<Set1>, <Set2>)", 035 "Returns the cross product of two sets.", 036 new String[]{"fxxx"}, 037 CrossJoinFunDef.class); 038 039 static final StarCrossJoinResolver StarResolver = 040 new StarCrossJoinResolver(); 041 042 private static int counterTag = 0; 043 044 // used to tell the difference between crossjoin expressions. 045 private final int ctag = counterTag++; 046 047 public CrossJoinFunDef(FunDef dummyFunDef) { 048 super(dummyFunDef); 049 } 050 051 public Type getResultType(Validator validator, Exp[] args) { 052 // CROSSJOIN(<Set1>,<Set2>) has type [Hie1] x [Hie2]. 053 List<MemberType> list = new ArrayList<MemberType>(); 054 for (Exp arg : args) { 055 final Type type = arg.getType(); 056 if (type instanceof SetType) { 057 addTypes(type, list); 058 } else if (getName().equals("*")) { 059 // The "*" form of CrossJoin is lenient: args can be either 060 // members/tuples or sets. 061 addTypes(type, list); 062 } else { 063 throw Util.newInternal("arg to crossjoin must be a set"); 064 } 065 } 066 final MemberType[] types = list.toArray(new MemberType[list.size()]); 067 TupleType.checkHierarchies(types); 068 final TupleType tupleType = new TupleType(types); 069 return new SetType(tupleType); 070 } 071 072 /** 073 * Adds a type to a list of types. If type is a {@link TupleType}, does so 074 * recursively. 075 * 076 * @param type Type to add to list 077 * @param list List of types to add to 078 */ 079 private static void addTypes(final Type type, List<MemberType> list) { 080 if (type instanceof SetType) { 081 SetType setType = (SetType) type; 082 addTypes(setType.getElementType(), list); 083 } else if (type instanceof TupleType) { 084 TupleType tupleType = (TupleType) type; 085 for (Type elementType : tupleType.elementTypes) { 086 addTypes(elementType, list); 087 } 088 } else if (type instanceof MemberType) { 089 list.add((MemberType) type); 090 } else { 091 throw Util.newInternal("Unexpected type: " + type); 092 } 093 } 094 095 public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) { 096 // What is the desired return type? 097 for (ResultStyle r : compiler.getAcceptableResultStyles()) { 098 switch (r) { 099 case ITERABLE: 100 case ANY: 101 // Consumer wants ITERABLE or ANY 102 return compileCallIterable(call, compiler); 103 case LIST: 104 // Consumer wants (immutable) LIST 105 return compileCallImmutableList(call, compiler); 106 case MUTABLE_LIST: 107 // Consumer MUTABLE_LIST 108 return compileCallMutableList(call, compiler); 109 } 110 } 111 throw ResultStyleException.generate( 112 ResultStyle.ITERABLE_LIST_MUTABLELIST_ANY, 113 compiler.getAcceptableResultStyles()); 114 } 115 116 /////////////////////////////////////////////////////////////////////////// 117 /////////////////////////////////////////////////////////////////////////// 118 // Iterable 119 /////////////////////////////////////////////////////////////////////////// 120 /////////////////////////////////////////////////////////////////////////// 121 122 protected IterCalc compileCallIterable( 123 final ResolvedFunCall call, 124 ExpCompiler compiler) 125 { 126 final Calc calc1 = toIter(compiler, call.getArg(0)); 127 final Calc calc2 = toIter(compiler, call.getArg(1)); 128 Calc[] calcs = new Calc[] {calc1, calc2}; 129 // The Calcs, 1 and 2, can be of type: Member or Member[] and 130 // of ResultStyle: ITERABLE, LIST or MUTABLE_LIST, but 131 // LIST and MUTABLE_LIST are treated the same; so 132 // there are 16 possible combinations - sweet. 133 134 // Check returned calc ResultStyles 135 checkIterListResultStyles(calc1); 136 checkIterListResultStyles(calc2); 137 138 return new CrossJoinIterCalc(call, calcs); 139 } 140 141 private Calc toIter(ExpCompiler compiler, final Exp exp) { 142 // Want iterable, immutable list or mutable list in that order 143 // It is assumed that an immutable list is easier to get than 144 // a mutable list. 145 final Type type = exp.getType(); 146 if (type instanceof SetType) { 147 // this can return an IterCalc or ListCalc 148 return compiler.compileAs( 149 exp, 150 null, 151 ResultStyle.ITERABLE_LIST_MUTABLELIST); 152 } else { 153 // this always returns an IterCalc 154 return new SetFunDef.ExprIterCalc( 155 new DummyExp(new SetType(type)), 156 new Exp[] {exp}, 157 compiler, 158 ResultStyle.ITERABLE_LIST_MUTABLELIST); 159 } 160 } 161 162 class CrossJoinIterCalc extends AbstractIterCalc 163 { 164 CrossJoinIterCalc(ResolvedFunCall call, Calc[] calcs) { 165 super(call, calcs); 166 } 167 168 public TupleIterable evaluateIterable(Evaluator evaluator) { 169 ResolvedFunCall call = (ResolvedFunCall) exp; 170 // Use a native evaluator, if more efficient. 171 // TODO: Figure this out at compile time. 172 SchemaReader schemaReader = evaluator.getSchemaReader(); 173 NativeEvaluator nativeEvaluator = 174 schemaReader.getNativeSetEvaluator( 175 call.getFunDef(), call.getArgs(), evaluator, this); 176 if (nativeEvaluator != null) { 177 return (TupleIterable) 178 nativeEvaluator.execute(ResultStyle.ITERABLE); 179 } 180 181 Calc[] calcs = getCalcs(); 182 IterCalc calc1 = (IterCalc) calcs[0]; 183 IterCalc calc2 = (IterCalc) calcs[1]; 184 185 TupleIterable o1 = calc1.evaluateIterable(evaluator); 186 if (o1 instanceof TupleList) { 187 TupleList l1 = (TupleList) o1; 188 l1 = nonEmptyOptimizeList(evaluator, l1, call); 189 if (l1.isEmpty()) { 190 return TupleCollections.emptyList(getType().getArity()); 191 } 192 o1 = l1; 193 } 194 195 TupleIterable o2 = calc2.evaluateIterable(evaluator); 196 if (o2 instanceof TupleList) { 197 TupleList l2 = (TupleList) o2; 198 l2 = nonEmptyOptimizeList(evaluator, l2, call); 199 if (l2.isEmpty()) { 200 return TupleCollections.emptyList(getType().getArity()); 201 } 202 o2 = l2; 203 } 204 205 return makeIterable(o1, o2); 206 } 207 208 protected TupleIterable makeIterable( 209 final TupleIterable it1, 210 final TupleIterable it2) 211 { 212 // There is no knowledge about how large either it1 ore it2 213 // are or how many null members they might have, so all 214 // one can do is iterate across them: 215 // iterate across it1 and for each member iterate across it2 216 217 return new AbstractTupleIterable(it1.getArity() + it2.getArity()) { 218 public TupleCursor tupleCursor() { 219 return new AbstractTupleCursor(getArity()) { 220 final TupleCursor i1 = it1.tupleCursor(); 221 final int arity1 = i1.getArity(); 222 TupleCursor i2 = 223 TupleCollections.emptyList(1).tupleCursor(); 224 final Member[] members = new Member[arity]; 225 226 public boolean forward() { 227 if (i2.forward()) { 228 return true; 229 } 230 while (i1.forward()) { 231 i2 = it2.tupleCursor(); 232 if (i2.forward()) { 233 return true; 234 } 235 } 236 return false; 237 } 238 239 public List<Member> current() { 240 i1.currentToArray(members, 0); 241 i2.currentToArray(members, arity1); 242 return Util.flatList(members); 243 } 244 245 @Override 246 public Member member(int column) { 247 if (column < arity1) { 248 return i1.member(column); 249 } else { 250 return i2.member(column - arity1); 251 } 252 } 253 254 @Override 255 public void setContext(Evaluator evaluator) { 256 i1.setContext(evaluator); 257 i2.setContext(evaluator); 258 } 259 260 @Override 261 public void currentToArray( 262 Member[] members, 263 int offset) 264 { 265 i1.currentToArray(members, offset); 266 i2.currentToArray(members, offset + arity1); 267 } 268 }; 269 } 270 }; 271 } 272 } 273 274 /////////////////////////////////////////////////////////////////////////// 275 // Immutable List 276 /////////////////////////////////////////////////////////////////////////// 277 278 protected ListCalc compileCallImmutableList( 279 final ResolvedFunCall call, 280 ExpCompiler compiler) 281 { 282 final ListCalc listCalc1 = toList(compiler, call.getArg(0)); 283 final ListCalc listCalc2 = toList(compiler, call.getArg(1)); 284 Calc[] calcs = new Calc[] {listCalc1, listCalc2}; 285 // The Calcs, 1 and 2, can be of type: Member or Member[] and 286 // of ResultStyle: LIST or MUTABLE_LIST. 287 // Since we want an immutable list as the result, it does not 288 // matter whether the Calc list are of type 289 // LIST and MUTABLE_LIST - they are treated the same; so 290 // there are 4 possible combinations - even sweeter. 291 292 // Check returned calc ResultStyles 293 checkListResultStyles(listCalc1); 294 checkListResultStyles(listCalc2); 295 296 return new ImmutableListCalc(call, calcs); 297 } 298 299 /** 300 * Compiles an expression to list (or mutable list) format. Never returns 301 * null. 302 * 303 * @param compiler Compiler 304 * @param exp Expression 305 * @return Compiled expression that yields a list or mutable list 306 */ 307 private ListCalc toList(ExpCompiler compiler, final Exp exp) { 308 // Want immutable list or mutable list in that order 309 // It is assumed that an immutable list is easier to get than 310 // a mutable list. 311 final Type type = exp.getType(); 312 if (type instanceof SetType) { 313 final Calc calc = compiler.compileAs( 314 exp, null, ResultStyle.LIST_MUTABLELIST); 315 if (calc == null) { 316 return compiler.compileList(exp, false); 317 } 318 return (ListCalc) calc; 319 } else { 320 return new SetFunDef.SetListCalc( 321 new DummyExp(new SetType(type)), 322 new Exp[] {exp}, 323 compiler, 324 ResultStyle.LIST_MUTABLELIST); 325 } 326 } 327 328 abstract class BaseListCalc extends AbstractListCalc { 329 protected BaseListCalc( 330 ResolvedFunCall call, 331 Calc[] calcs, 332 boolean mutable) 333 { 334 super(call, calcs, mutable); 335 } 336 337 public TupleList evaluateList(Evaluator evaluator) { 338 ResolvedFunCall call = (ResolvedFunCall) exp; 339 // Use a native evaluator, if more efficient. 340 // TODO: Figure this out at compile time. 341 SchemaReader schemaReader = evaluator.getSchemaReader(); 342 NativeEvaluator nativeEvaluator = 343 schemaReader.getNativeSetEvaluator( 344 call.getFunDef(), call.getArgs(), evaluator, this); 345 if (nativeEvaluator != null) { 346 return (TupleList) nativeEvaluator.execute(ResultStyle.LIST); 347 } 348 349 Calc[] calcs = getCalcs(); 350 ListCalc listCalc1 = (ListCalc) calcs[0]; 351 ListCalc listCalc2 = (ListCalc) calcs[1]; 352 353 TupleList l1 = listCalc1.evaluateList(evaluator); 354 TupleList l2 = listCalc2.evaluateList(evaluator); 355 356 l1 = nonEmptyOptimizeList(evaluator, l1, call); 357 if (l1.isEmpty()) { 358 return TupleCollections.emptyList( 359 l1.getArity() + l2.getArity()); 360 } 361 l2 = nonEmptyOptimizeList(evaluator, l2, call); 362 if (l2.isEmpty()) { 363 return TupleCollections.emptyList( 364 l1.getArity() + l2.getArity()); 365 } 366 367 return makeList(l1, l2); 368 } 369 370 protected abstract TupleList makeList(TupleList l1, TupleList l2); 371 } 372 373 class ImmutableListCalc 374 extends BaseListCalc 375 { 376 ImmutableListCalc( 377 ResolvedFunCall call, Calc[] calcs) 378 { 379 super(call, calcs, false); 380 } 381 382 protected TupleList makeList(final TupleList l1, final TupleList l2) { 383 final int arity = l1.getArity() + l2.getArity(); 384 return new DelegatingTupleList( 385 arity, 386 new AbstractList<List<Member>>() { 387 final List<List<List<Member>>> lists = 388 Arrays.<List<List<Member>>>asList( 389 l1, l2); 390 final Member[] members = new Member[arity]; 391 392 final CartesianProductList cartesianProductList = 393 new CartesianProductList<List<Member>>( 394 lists); 395 396 @Override 397 public List<Member> get(int index) { 398 cartesianProductList.getIntoArray(index, members); 399 return Util.flatList(members); 400 } 401 402 @Override 403 public int size() { 404 return cartesianProductList.size(); 405 } 406 }); 407 } 408 } 409 410 protected ListCalc compileCallMutableList( 411 final ResolvedFunCall call, 412 ExpCompiler compiler) 413 { 414 final ListCalc listCalc1 = toList(compiler, call.getArg(0)); 415 final ListCalc listCalc2 = toList(compiler, call.getArg(1)); 416 417 Calc[] calcs = new Calc[] {listCalc1, listCalc2}; 418 // The Calcs, 1 and 2, can be of type: Member or Member[] and 419 // of ResultStyle: LIST or MUTABLE_LIST. 420 // Since we want an mutable list as the result, it does not 421 // matter whether the Calc list are of type 422 // LIST and MUTABLE_LIST - they are treated the same, 423 // regardless of type, one must materialize the result list; so 424 // there are 4 possible combinations - even sweeter. 425 426 // Check returned calc ResultStyles 427 checkListResultStyles(listCalc1); 428 checkListResultStyles(listCalc2); 429 430 return new MutableListCalc(call, calcs); 431 } 432 433 class MutableListCalc extends BaseListCalc 434 { 435 MutableListCalc(ResolvedFunCall call, Calc[] calcs) 436 { 437 super(call, calcs, true); 438 } 439 440 @SuppressWarnings({"unchecked"}) 441 protected TupleList makeList(final TupleList l1, final TupleList l2) { 442 final int arity = l1.getArity() + l2.getArity(); 443 final List<Member> members = 444 new ArrayList<Member>(arity * l1.size() * l2.size()); 445 for (List<Member> ma1 : l1) { 446 for (List<Member> ma2 : l2) { 447 members.addAll(ma1); 448 members.addAll(ma2); 449 } 450 } 451 return new ListTupleList(arity, members); 452 } 453 } 454 455 protected TupleList nonEmptyOptimizeList( 456 Evaluator evaluator, 457 TupleList list, 458 ResolvedFunCall call) 459 { 460 int opSize = MondrianProperties.instance().CrossJoinOptimizerSize.get(); 461 if (list.isEmpty()) { 462 return list; 463 } 464 try { 465 final Object o = list.get(0); 466 if (o instanceof Member) { 467 // Cannot optimize high cardinality dimensions 468 if (((Member)o).getDimension().isHighCardinality()) { 469 return list; 470 } 471 } 472 } catch (IndexOutOfBoundsException ioobe) { 473 return TupleCollections.emptyList(list.getArity()); 474 } 475 int size = list.size(); 476 477 if (size > opSize && evaluator.isNonEmpty()) { 478 // instead of overflow exception try to further 479 // optimize nonempty(crossjoin(a,b)) == 480 // nonempty(crossjoin(nonempty(a),nonempty(b)) 481 final int missCount = evaluator.getMissCount(); 482 483 list = nonEmptyList(evaluator, list, call); 484 size = list.size(); 485 // list may be empty after nonEmpty optimization 486 if (size == 0) { 487 return TupleCollections.emptyList(list.getArity()); 488 } 489 final int missCount2 = evaluator.getMissCount(); 490 final int puntMissCountListSize = 1000; 491 if (missCount2 > missCount && size > puntMissCountListSize) { 492 // We've hit some cells which are not in the cache. They 493 // registered as non-empty, but we won't really know until 494 // we've populated the cache. The cartesian product is still 495 // huge, so let's quit now, and try again after the cache 496 // has been loaded. 497 // Return an empty list short circuits higher level 498 // evaluation poping one all the way to the top. 499 return TupleCollections.emptyList(list.getArity()); 500 } 501 } 502 return list; 503 } 504 505 public static TupleList mutableCrossJoin( 506 TupleList list1, 507 TupleList list2) 508 { 509 return mutableCrossJoin(Arrays.asList(list1, list2)); 510 } 511 512 public static TupleList mutableCrossJoin( 513 List<TupleList> lists) 514 { 515 long size = 1; 516 int arity = 0; 517 for (TupleList list : lists) { 518 size *= (long) list.size(); 519 arity += list.getArity(); 520 } 521 if (size == 0L) { 522 return TupleCollections.emptyList(arity); 523 } 524 525 // Optimize nonempty(crossjoin(a,b)) == 526 // nonempty(crossjoin(nonempty(a),nonempty(b)) 527 528 // FIXME: If we're going to apply a NON EMPTY constraint later, it's 529 // possible that the ultimate result will be much smaller. 530 531 Util.checkCJResultLimit(size); 532 533 // Now we can safely cast size to an integer. It still might be very 534 // large - which means we're allocating a huge array which we might 535 // pare down later by applying NON EMPTY constraints - which is a 536 // concern. 537 List<Member> result = new ArrayList<Member>((int) size * arity); 538 539 final Member[] partialArray = new Member[arity]; 540 final List<Member> partial = Arrays.asList(partialArray); 541 cartesianProductRecurse(0, lists, partial, partialArray, 0, result); 542 return new ListTupleList(arity, result); 543 } 544 545 private static void cartesianProductRecurse( 546 int i, 547 List<TupleList> lists, 548 List<Member> partial, 549 Member[] partialArray, 550 int partialSize, 551 List<Member> result) 552 { 553 final TupleList tupleList = lists.get(i); 554 final int partialSizeNext = partialSize + tupleList.getArity(); 555 final int iNext = i + 1; 556 final TupleCursor cursor = tupleList.tupleCursor(); 557 while (cursor.forward()) { 558 cursor.currentToArray(partialArray, partialSize); 559 if (i == lists.size() - 1) { 560 result.addAll(partial); 561 } else { 562 cartesianProductRecurse( 563 iNext, lists, partial, partialArray, partialSizeNext, 564 result); 565 } 566 } 567 } 568 569 /** 570 * Visitor class used to locate a resolved function call within an 571 * expression 572 */ 573 private static class ResolvedFunCallFinder 574 extends MdxVisitorImpl 575 { 576 private final ResolvedFunCall call; 577 public boolean found; 578 private final Set<Member> activeMembers = new HashSet<Member>(); 579 580 public ResolvedFunCallFinder(ResolvedFunCall call) 581 { 582 this.call = call; 583 found = false; 584 } 585 586 public Object visit(ResolvedFunCall funCall) 587 { 588 if (funCall == call) { 589 found = true; 590 } 591 return null; 592 } 593 594 public Object visit(MemberExpr memberExpr) { 595 Member member = memberExpr.getMember(); 596 if (member.isCalculated()) { 597 if (activeMembers.add(member)) { 598 Exp memberExp = member.getExpression(); 599 memberExp.accept(this); 600 activeMembers.remove(member); 601 } 602 } 603 return null; 604 } 605 } 606 607 /** 608 * Traverses the function call tree of 609 * the non empty crossjoin function and populates the queryMeasureSet 610 * with base measures 611 */ 612 private static class MeasureVisitor extends MdxVisitorImpl { 613 614 private final Set<Member> queryMeasureSet; 615 private final ResolvedFunCallFinder finder; 616 private final Set<Member> activeMeasures = new HashSet<Member>(); 617 618 /** 619 * Creates a MeasureVisitor. 620 * 621 * @param queryMeasureSet Set of measures in query 622 * 623 * @param crossJoinCall Measures referencing this call should be 624 * excluded from the list of measures found 625 */ 626 MeasureVisitor( 627 Set<Member> queryMeasureSet, 628 ResolvedFunCall crossJoinCall) 629 { 630 this.queryMeasureSet = queryMeasureSet; 631 this.finder = new ResolvedFunCallFinder(crossJoinCall); 632 } 633 634 public Object visit(ParameterExpr parameterExpr) { 635 final Parameter parameter = parameterExpr.getParameter(); 636 final Type type = parameter.getType(); 637 if (type instanceof mondrian.olap.type.MemberType) { 638 final Object value = parameter.getValue(); 639 if (value instanceof Member) { 640 final Member member = (Member) value; 641 process(member); 642 } 643 } 644 645 return null; 646 } 647 648 public Object visit(MemberExpr memberExpr) { 649 Member member = memberExpr.getMember(); 650 process(member); 651 return null; 652 } 653 654 private void process(final Member member) { 655 if (member.isMeasure()) { 656 if (member.isCalculated()) { 657 if (activeMeasures.add(member)) { 658 Exp exp = member.getExpression(); 659 finder.found = false; 660 exp.accept(finder); 661 if (! finder.found) { 662 exp.accept(this); 663 } 664 activeMeasures.remove(member); 665 } 666 } else { 667 queryMeasureSet.add(member); 668 } 669 } 670 } 671 } 672 673 /** 674 * This is the entry point to the crossjoin non-empty optimizer code. 675 * 676 * <p>What one wants to determine is for each individual Member of the input 677 * parameter list, a 'List-Member', whether across a slice there is any 678 * data. 679 * 680 * <p>But what data? 681 * 682 * <p>For Members other than those in the list, the 'non-List-Members', 683 * one wants to consider 684 * all data across the scope of these other Members. For instance, if 685 * Time is not a List-Member, then one wants to consider data 686 * across All Time. Or, if Customer is not a List-Member, then 687 * look at data across All Customers. The theory here, is if there 688 * is no data for a particular Member of the list where all other 689 * Members not part of the list are span their complete hierarchy, then 690 * there is certainly no data for Members of that Hierarchy at a 691 * more specific Level (more on this below). 692 * 693 * <p>When a Member that is a non-List-Member is part of a Hierarchy 694 * that has an 695 * All Member (hasAll="true"), then its very easy to make sure that 696 * the All Member is used during the optimization. 697 * If a non-List-Member is part of a Hierarchy that does not have 698 * an All Member, then one must, in fact, iterate over all top-level 699 * Members of the Hierarchy!!! - otherwise a List-Member might 700 * be excluded because the optimization code was not looking everywhere. 701 * 702 * <p>Concerning default Members for those Hierarchies for the 703 * non-List-Members, ignore them. What is wanted is either the 704 * All Member or one must iterate across all top-level Members, what 705 * happens to be the default Member of the Hierarchy is of no relevant. 706 * 707 * <p>The Measures Hierarchy has special considerations. First, there is 708 * no All Measure. But, certainly one need only involve Measures 709 * that are actually in the query... yes and no. For Calculated Measures 710 * one must also get all of the non-Calculated Measures that make up 711 * each Calculated Measure. Thus, one ends up iterating across all 712 * Calculated and non-Calculated Measures that are explicitly 713 * mentioned in the query as well as all Calculated and non-Calculated 714 * Measures that are used to define the Calculated Measures in 715 * the query. Why all of these? because this represents the total 716 * scope of possible Measures that might yield a non-null value 717 * for the List-Members and that is what we what to find. It might 718 * be a super set, but thats ok; we just do not want to miss anything. 719 * 720 * <p>For other Members, the default Member is used, but for Measures one 721 * should look for that data for all Measures associated with the query, not 722 * just one Measure. For a dense dataset this may not be a problem or even 723 * apparent, but for a sparse dataset, the first Measure may, in fact, have 724 * not data but other Measures associated with the query might. 725 * Hence, the solution here is to identify all Measures associated with the 726 * query and then for each Member of the list, determine if there is any 727 * data iterating across all Measures until non-null data is found or the 728 * end of the Measures is reached. 729 * 730 * <p>This is a non-optimistic implementation. This means that an 731 * element of the input parameter List is only not included in the 732 * returned result List if for no combination of Measures, non-All 733 * Members (for Hierarchies that have no All Members) and evaluator 734 * default Members did the element evaluate to non-null. 735 * 736 * @param evaluator Evaluator 737 * 738 * @param list List of members or tuples 739 * 740 * @param call Calling ResolvedFunCall used to determine what Measures 741 * to use 742 * 743 * @return List of elements from the input parameter list that have 744 * evaluated to non-null. 745 */ 746 protected TupleList nonEmptyList( 747 Evaluator evaluator, 748 TupleList list, 749 ResolvedFunCall call) 750 { 751 if (list.isEmpty()) { 752 return list; 753 } 754 755 TupleList result = 756 TupleCollections.createList( 757 list.getArity(), (list.size() + 2) >> 1); 758 759 // Get all of the Measures 760 final Query query = evaluator.getQuery(); 761 762 final String measureSetKey = "MEASURE_SET-" + ctag; 763 Set<Member> measureSet = 764 Util.cast((Set) query.getEvalCache(measureSetKey)); 765 // If not in query cache, then create and place into cache. 766 // This information is used for each iteration so it makes 767 // sense to create and cache it. 768 if (measureSet == null) { 769 measureSet = new HashSet<Member>(); 770 Set<Member> queryMeasureSet = query.getMeasuresMembers(); 771 MeasureVisitor visitor = new MeasureVisitor(measureSet, call); 772 for (Member m : queryMeasureSet) { 773 if (m.isCalculated()) { 774 Exp exp = m.getExpression(); 775 exp.accept(visitor); 776 } else { 777 measureSet.add(m); 778 } 779 } 780 781 Formula[] formula = query.getFormulas(); 782 if (formula != null) { 783 for (Formula f : formula) { 784 f.accept(visitor); 785 } 786 } 787 788 query.putEvalCache(measureSetKey, measureSet); 789 } 790 791 final String allMemberListKey = "ALL_MEMBER_LIST-" + ctag; 792 List<Member> allMemberList = 793 Util.cast((List) query.getEvalCache(allMemberListKey)); 794 795 final String nonAllMembersKey = "NON_ALL_MEMBERS-" + ctag; 796 Member[][] nonAllMembers = 797 (Member[][]) query.getEvalCache(nonAllMembersKey); 798 if (nonAllMembers == null) { 799 // 800 // Get all of the All Members and those Hierarchies that 801 // do not have All Members. 802 // 803 Member[] evalMembers = evaluator.getMembers().clone(); 804 805 List<Member> listMembers = list.get(0); 806 807 // Remove listMembers from evalMembers and independentSlicerMembers 808 for (Member lm : listMembers) { 809 Hierarchy h = lm.getHierarchy(); 810 for (int i = 0; i < evalMembers.length; i++) { 811 Member em = evalMembers[i]; 812 if ((em != null) && h.equals(em.getHierarchy())) { 813 evalMembers[i] = null; 814 } 815 } 816 } 817 818 List<Member> slicerMembers = null; 819 if (evaluator instanceof RolapEvaluator) { 820 RolapEvaluator rev = (RolapEvaluator) evaluator; 821 slicerMembers = rev.getSlicerMembers(); 822 } 823 // Iterate the list of slicer members, grouping them by hierarchy 824 Map<Hierarchy, Set<Member>> mapOfSlicerMembers = 825 new HashMap<Hierarchy, Set<Member>>(); 826 if (slicerMembers != null) { 827 for (Member slicerMember : slicerMembers) { 828 Hierarchy hierarchy = slicerMember.getHierarchy(); 829 if (!mapOfSlicerMembers.containsKey(hierarchy)) { 830 mapOfSlicerMembers.put( 831 hierarchy, 832 new HashSet<Member>()); 833 } 834 mapOfSlicerMembers.get(hierarchy).add(slicerMember); 835 } 836 } 837 838 // Now we have the non-List-Members, but some of them may not be 839 // All Members (default Member need not be the All Member) and 840 // for some Hierarchies there may not be an All Member. 841 // So we create an array of Objects some elements of which are 842 // All Members and others elements will be an array of all top-level 843 // Members when there is not an All Member. 844 SchemaReader schemaReader = evaluator.getSchemaReader(); 845 allMemberList = new ArrayList<Member>(); 846 List<Member[]> nonAllMemberList = new ArrayList<Member[]>(); 847 848 Member em; 849 boolean isSlicerMember; 850 for (Member evalMember : evalMembers) { 851 em = evalMember; 852 853 isSlicerMember = 854 slicerMembers != null 855 && slicerMembers.contains(em); 856 857 if (em == null) { 858 // Above we might have removed some by setting them 859 // to null. These are the CrossJoin axes. 860 continue; 861 } 862 if (em.isMeasure()) { 863 continue; 864 } 865 866 // 867 // The unconstrained members need to be replaced by the "All" 868 // member based on its usage and property. This is currently 869 // also the behavior of native cross join evaluation. See 870 // SqlConstraintUtils.addContextConstraint() 871 // 872 // on slicer? | calculated? | replace with All? 873 // ----------------------------------------------- 874 // Y | Y | Y always 875 // Y | N | N 876 // N | Y | N 877 // N | N | Y if not "All" 878 // ----------------------------------------------- 879 // 880 if ((isSlicerMember && !em.isCalculated()) 881 || (!isSlicerMember && em.isCalculated())) 882 { 883 // If the slicer contains multiple members from this one's 884 // hierarchy, add them to nonAllMemberList 885 if (isSlicerMember) { 886 Set<Member> hierarchySlicerMembers = 887 mapOfSlicerMembers.get(em.getHierarchy()); 888 if (hierarchySlicerMembers.size() > 1) { 889 nonAllMemberList.add( 890 hierarchySlicerMembers.toArray( 891 new Member[hierarchySlicerMembers.size()])); 892 } 893 } 894 continue; 895 } 896 897 // If the member is not the All member; 898 // or if it is a slicer member, 899 // replace with the "all" member. 900 if (isSlicerMember || !em.isAll()) { 901 Hierarchy h = em.getHierarchy(); 902 final List<Member> rootMemberList = 903 schemaReader.getHierarchyRootMembers(h); 904 if (h.hasAll()) { 905 // The Hierarchy has an All member 906 boolean found = false; 907 for (Member m : rootMemberList) { 908 if (m.isAll()) { 909 allMemberList.add(m); 910 found = true; 911 break; 912 } 913 } 914 if (!found) { 915 System.out.println( 916 "CrossJoinFunDef.nonEmptyListNEW: ERROR"); 917 } 918 } else { 919 // The Hierarchy does NOT have an All member 920 Member[] rootMembers = 921 rootMemberList.toArray( 922 new Member[rootMemberList.size()]); 923 nonAllMemberList.add(rootMembers); 924 } 925 } 926 } 927 nonAllMembers = 928 nonAllMemberList.toArray( 929 new Member[nonAllMemberList.size()][]); 930 931 query.putEvalCache(allMemberListKey, allMemberList); 932 query.putEvalCache(nonAllMembersKey, nonAllMembers); 933 } 934 935 // 936 // Determine if there is any data. 937 // 938 // Put all of the All Members into Evaluator 939 final int savepoint = evaluator.savepoint(); 940 try { 941 evaluator.setContext(allMemberList); 942 // Iterate over elements of the input list. If for any 943 // combination of 944 // Measure and non-All Members evaluation is non-null, then 945 // add it to the result List. 946 final TupleCursor cursor = list.tupleCursor(); 947 while (cursor.forward()) { 948 cursor.setContext(evaluator); 949 if (checkData( 950 nonAllMembers, 951 nonAllMembers.length - 1, 952 measureSet, 953 evaluator)) 954 { 955 result.addCurrent(cursor); 956 } 957 } 958 return result; 959 } finally { 960 evaluator.restore(savepoint); 961 } 962 } 963 964 /** 965 * Return <code>true</code> if for some combination of Members 966 * from the nonAllMembers array of Member arrays and Measures from 967 * the Set of Measures evaluate to a non-null value. Even if a 968 * particular combination is non-null, all combinations are tested 969 * just to make sure that the data is loaded. 970 * 971 * @param nonAllMembers array of Member arrays of top-level Members 972 * for Hierarchies that have no All Member. 973 * @param cnt which Member array is to be processed. 974 * @param measureSet Set of all that should be tested against. 975 * @param evaluator the Evaluator. 976 * @return True if at least one combination evaluated to non-null. 977 */ 978 private static boolean checkData( 979 Member[][] nonAllMembers, 980 int cnt, 981 Set<Member> measureSet, 982 Evaluator evaluator) 983 { 984 if (cnt < 0) { 985 // no measures found, use standard algorithm 986 if (measureSet.isEmpty()) { 987 Object value = evaluator.evaluateCurrent(); 988 if (value != null 989 && !(value instanceof Throwable)) 990 { 991 return true; 992 } 993 } else { 994 // Here we evaluate across all measures just to 995 // make sure that the data is all loaded 996 boolean found = false; 997 for (Member measure : measureSet) { 998 evaluator.setContext(measure); 999 Object value = evaluator.evaluateCurrent(); 1000 if (value != null 1001 && !(value instanceof Throwable)) 1002 { 1003 found = true; 1004 } 1005 } 1006 return found; 1007 } 1008 } else { 1009 boolean found = false; 1010 for (Member m : nonAllMembers[cnt]) { 1011 evaluator.setContext(m); 1012 if (checkData(nonAllMembers, cnt - 1, measureSet, evaluator)) { 1013 found = true; 1014 } 1015 } 1016 return found; 1017 } 1018 return false; 1019 } 1020 1021 private static class StarCrossJoinResolver extends MultiResolver { 1022 public StarCrossJoinResolver() { 1023 super( 1024 "*", 1025 "<Set1> * <Set2>", 1026 "Returns the cross product of two sets.", 1027 new String[]{"ixxx", "ixmx", "ixxm", "ixmm"}); 1028 } 1029 1030 public FunDef resolve( 1031 Exp[] args, 1032 Validator validator, 1033 List<Conversion> conversions) 1034 { 1035 // This function only applies in contexts which require a set. 1036 // Elsewhere, "*" is the multiplication operator. 1037 // This means that [Measures].[Unit Sales] * [Gender].[M] is 1038 // well-defined. 1039 if (validator.requiresExpression()) { 1040 return null; 1041 } 1042 return super.resolve(args, validator, conversions); 1043 } 1044 1045 protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { 1046 return new CrossJoinFunDef(dummyFunDef); 1047 } 1048 } 1049} 1050 1051// End CrossJoinFunDef.java