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) 2005-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho 009// All Rights Reserved. 010*/ 011package mondrian.olap.fun; 012 013import mondrian.calc.*; 014import mondrian.calc.impl.*; 015import mondrian.mdx.ResolvedFunCall; 016import mondrian.olap.*; 017import mondrian.olap.type.TupleType; 018import mondrian.olap.type.Type; 019import mondrian.rolap.RolapUtil; 020 021import java.io.PrintWriter; 022import java.util.*; 023 024/** 025 * Definition of the <code>RANK</code> MDX function. 026 * 027 * @author Richard Emberson 028 * @since 17 January, 2005 029 */ 030public class RankFunDef extends FunDefBase { 031 static final boolean debug = false; 032 static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( 033 "Rank", 034 "Rank(<Tuple>, <Set> [, <Calc Expression>])", 035 "Returns the one-based rank of a tuple in a set.", 036 new String[]{"fitx", "fitxn", "fimx", "fimxn"}, 037 RankFunDef.class); 038 039 public RankFunDef(FunDef dummyFunDef) { 040 super(dummyFunDef); 041 } 042 043 public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { 044 switch (call.getArgCount()) { 045 case 2: 046 return compileCall2(call, compiler); 047 case 3: 048 return compileCall3(call, compiler); 049 default: 050 throw Util.newInternal("invalid arg count " + call.getArgCount()); 051 } 052 } 053 054 public Calc compileCall3(ResolvedFunCall call, ExpCompiler compiler) { 055 final Type type0 = call.getArg(0).getType(); 056 final ListCalc listCalc = 057 compiler.compileList(call.getArg(1)); 058 final Calc keyCalc = 059 compiler.compileScalar(call.getArg(2), true); 060 Calc sortedListCalc = 061 new SortedListCalc(call, listCalc, keyCalc); 062 final ExpCacheDescriptor cacheDescriptor = 063 new ExpCacheDescriptor( 064 call, sortedListCalc, compiler.getEvaluator()); 065 if (type0 instanceof TupleType) { 066 final TupleCalc tupleCalc = 067 compiler.compileTuple(call.getArg(0)); 068 return new Rank3TupleCalc( 069 call, tupleCalc, keyCalc, cacheDescriptor); 070 } else { 071 final MemberCalc memberCalc = 072 compiler.compileMember(call.getArg(0)); 073 return new Rank3MemberCalc( 074 call, memberCalc, keyCalc, cacheDescriptor); 075 } 076 } 077 078 public Calc compileCall2(ResolvedFunCall call, ExpCompiler compiler) { 079 final boolean tuple = call.getArg(0).getType() instanceof TupleType; 080 final Exp listExp = call.getArg(1); 081 final ListCalc listCalc0 = compiler.compileList(listExp); 082 Calc listCalc1 = new RankedListCalc(listCalc0, tuple); 083 final Calc listCalc; 084 if (MondrianProperties.instance().EnableExpCache.get()) { 085 final ExpCacheDescriptor key = new ExpCacheDescriptor( 086 listExp, listCalc1, compiler.getEvaluator()); 087 listCalc = new CacheCalc(listExp, key); 088 } else { 089 listCalc = listCalc1; 090 } 091 if (tuple) { 092 final TupleCalc tupleCalc = 093 compiler.compileTuple(call.getArg(0)); 094 return new Rank2TupleCalc(call, tupleCalc, listCalc); 095 } else { 096 final MemberCalc memberCalc = 097 compiler.compileMember(call.getArg(0)); 098 return new Rank2MemberCalc(call, memberCalc, listCalc); 099 } 100 } 101 102 private static class Rank2TupleCalc extends AbstractIntegerCalc { 103 private final TupleCalc tupleCalc; 104 private final Calc listCalc; 105 106 public Rank2TupleCalc( 107 ResolvedFunCall call, TupleCalc tupleCalc, Calc listCalc) 108 { 109 super(call, new Calc[] {tupleCalc, listCalc}); 110 this.tupleCalc = tupleCalc; 111 this.listCalc = listCalc; 112 } 113 114 public int evaluateInteger(Evaluator evaluator) { 115 // Get member or tuple. 116 // If the member is null (or the tuple contains a null member) 117 // the result is null (even if the list is null). 118 final Member[] members = tupleCalc.evaluateTuple(evaluator); 119 if (members == null) { 120 return IntegerNull; 121 } 122 assert !tupleContainsNullMember(members); 123 124 // Get the set of members/tuples. 125 // If the list is empty, MSAS cannot figure out the type of the 126 // list, so returns an error "Formula error - dimension count is 127 // not valid - in the Rank function". We will naturally return 0, 128 // which I think is better. 129 final RankedTupleList rankedTupleList = 130 (RankedTupleList) listCalc.evaluate(evaluator); 131 if (rankedTupleList == null) { 132 return 0; 133 } 134 135 // Find position of member in list. -1 signifies not found. 136 final List<Member> memberList = Arrays.asList(members); 137 final int i = rankedTupleList.indexOf(memberList); 138 // Return 1-based rank. 0 signifies not found. 139 return i + 1; 140 } 141 } 142 143 private static class Rank2MemberCalc extends AbstractIntegerCalc { 144 private final MemberCalc memberCalc; 145 private final Calc listCalc; 146 147 public Rank2MemberCalc( 148 ResolvedFunCall call, MemberCalc memberCalc, Calc listCalc) 149 { 150 super(call, new Calc[] {memberCalc, listCalc}); 151 this.memberCalc = memberCalc; 152 this.listCalc = listCalc; 153 } 154 155 public int evaluateInteger(Evaluator evaluator) { 156 // Get member or tuple. 157 // If the member is null (or the tuple contains a null member) 158 // the result is null (even if the list is null). 159 final Member member = memberCalc.evaluateMember(evaluator); 160 if (member == null 161 || member.isNull()) 162 { 163 return IntegerNull; 164 } 165 // Get the set of members/tuples. 166 // If the list is empty, MSAS cannot figure out the type of the 167 // list, so returns an error "Formula error - dimension count is 168 // not valid - in the Rank function". We will naturally return 0, 169 // which I think is better. 170 RankedMemberList rankedMemberList = 171 (RankedMemberList) listCalc.evaluate(evaluator); 172 if (rankedMemberList == null) { 173 return 0; 174 } 175 176 // Find position of member in list. -1 signifies not found. 177 final int i = rankedMemberList.indexOf(member); 178 // Return 1-based rank. 0 signifies not found. 179 return i + 1; 180 } 181 } 182 183 private static class Rank3TupleCalc extends AbstractIntegerCalc { 184 private final TupleCalc tupleCalc; 185 private final Calc sortCalc; 186 private final ExpCacheDescriptor cacheDescriptor; 187 188 public Rank3TupleCalc( 189 ResolvedFunCall call, 190 TupleCalc tupleCalc, 191 Calc sortCalc, 192 ExpCacheDescriptor cacheDescriptor) 193 { 194 super(call, new Calc[] {tupleCalc, sortCalc}); 195 this.tupleCalc = tupleCalc; 196 this.sortCalc = sortCalc; 197 this.cacheDescriptor = cacheDescriptor; 198 } 199 200 public int evaluateInteger(Evaluator evaluator) { 201 Member[] members = tupleCalc.evaluateTuple(evaluator); 202 if (members == null) { 203 return IntegerNull; 204 } 205 assert !tupleContainsNullMember(members); 206 207 // Evaluate the list (or retrieve from cache). 208 // If there is an exception while calculating the 209 // list, propagate it up. 210 final TupleSortResult sortResult = 211 (TupleSortResult) evaluator.getCachedResult(cacheDescriptor); 212 if (debug) { 213 sortResult.print(new PrintWriter(System.out)); 214 } 215 216 if (sortResult.isEmpty()) { 217 // If list is empty, the rank is null. 218 return IntegerNull; 219 } 220 221 // First try to find the member in the cached SortResult 222 Integer rank = sortResult.rankOf(members); 223 if (rank != null) { 224 return rank; 225 } 226 227 // member is not seen before, now compute the value of the tuple. 228 final int savepoint = evaluator.savepoint(); 229 Object value; 230 try { 231 evaluator.setContext(members); 232 value = sortCalc.evaluate(evaluator); 233 } finally { 234 evaluator.restore(savepoint); 235 } 236 237 if (valueNotReady(value)) { 238 // The value wasn't ready, so quit now... we'll be back. 239 return 0; 240 } 241 242 // If value is null, it won't be in the values array. 243 if (value == Util.nullValue || value == null) { 244 return sortResult.values.length + 1; 245 } 246 247 value = coerceValue(sortResult.values, value); 248 249 // Look for the ranked value in the array. 250 int j = Arrays.binarySearch( 251 sortResult.values, value, Collections.<Object>reverseOrder()); 252 if (j < 0) { 253 // Value not found. Flip the result to find the 254 // insertion point. 255 j = -(j + 1); 256 } 257 return j + 1; // 1-based 258 } 259 } 260 261 private static class Rank3MemberCalc extends AbstractIntegerCalc { 262 private final MemberCalc memberCalc; 263 private final Calc sortCalc; 264 private final ExpCacheDescriptor cacheDescriptor; 265 266 public Rank3MemberCalc( 267 ResolvedFunCall call, 268 MemberCalc memberCalc, 269 Calc sortCalc, 270 ExpCacheDescriptor cacheDescriptor) 271 { 272 super(call, new Calc[] {memberCalc, sortCalc}); 273 this.memberCalc = memberCalc; 274 this.sortCalc = sortCalc; 275 this.cacheDescriptor = cacheDescriptor; 276 } 277 278 public int evaluateInteger(Evaluator evaluator) { 279 Member member = memberCalc.evaluateMember(evaluator); 280 if (member == null || member.isNull()) { 281 return IntegerNull; 282 } 283 284 // Evaluate the list (or retrieve from cache). 285 // If there was an exception while calculating the 286 // list, propagate it up. 287 final MemberSortResult sortResult = 288 (MemberSortResult) evaluator.getCachedResult(cacheDescriptor); 289 if (debug) { 290 sortResult.print(new PrintWriter(System.out)); 291 } 292 if (sortResult.isEmpty()) { 293 // If list is empty, the rank is null. 294 return IntegerNull; 295 } 296 297 // First try to find the member in the cached SortResult 298 Integer rank = sortResult.rankOf(member); 299 if (rank != null) { 300 return rank; 301 } 302 303 // member is not seen before, now compute the value of the tuple. 304 final int savepoint = evaluator.savepoint(); 305 evaluator.setContext(member); 306 Object value; 307 try { 308 value = sortCalc.evaluate(evaluator); 309 } finally { 310 evaluator.restore(savepoint); 311 } 312 313 if (valueNotReady(value)) { 314 // The value wasn't ready, so quit now... we'll be back. 315 return 0; 316 } 317 318 // If value is null, it won't be in the values array. 319 if (value == Util.nullValue || value == null) { 320 return sortResult.values.length + 1; 321 } 322 323 value = coerceValue(sortResult.values, value); 324 325 // Look for the ranked value in the array. 326 int j = Arrays.binarySearch( 327 sortResult.values, value, Collections.<Object>reverseOrder()); 328 if (j < 0) { 329 // Value not found. Flip the result to find the 330 // insertion point. 331 j = -(j + 1); 332 } 333 return j + 1; // 1-based 334 } 335 } 336 337 private static Object coerceValue(Object[] values, Object value) { 338 if (values.length > 0) { 339 final Object firstValue = values[0]; 340 if (firstValue instanceof Integer && value instanceof Double) { 341 return ((Double) value).intValue(); 342 } 343 } 344 return value; 345 } 346 347 private static boolean valueNotReady(Object value) { 348 return value == RolapUtil.valueNotReadyException 349 || value == new Double(Double.NaN); 350 } 351 352 /** 353 * Calc which evaluates an expression to form a list of tuples, 354 * evaluates a scalar expression at each tuple, then sorts the list of 355 * values. The result is a value of type {@link SortResult}, and can be 356 * used to implement the <code>Rank</code> function efficiently. 357 */ 358 private static class SortedListCalc extends AbstractCalc { 359 private final ListCalc listCalc; 360 private final Calc keyCalc; 361 362 private static final Integer ONE = 1; 363 364 /** 365 * Creates a SortCalc. 366 * 367 * @param exp Source expression 368 * @param listCalc Compiled expression to compute the list 369 * @param keyCalc Compiled expression to compute the sort key 370 */ 371 public SortedListCalc( 372 Exp exp, 373 ListCalc listCalc, 374 Calc keyCalc) 375 { 376 super(exp, new Calc[] {listCalc, keyCalc}); 377 this.listCalc = listCalc; 378 this.keyCalc = keyCalc; 379 } 380 381 public boolean dependsOn(Hierarchy hierarchy) { 382 return anyDependsButFirst(getCalcs(), hierarchy); 383 } 384 385 public Object evaluate(Evaluator evaluator) { 386 // Save the state of the evaluator. 387 final int savepoint = evaluator.savepoint(); 388 RuntimeException exception = null; 389 final Map<Member, Object> memberValueMap; 390 final Map<List<Member>, Object> tupleValueMap; 391 final int numValues; 392 //noinspection unchecked 393 final Map<Object, Integer> uniqueValueCounterMap = 394 new TreeMap<Object, Integer>( 395 FunUtil.DescendingValueComparator.instance); 396 TupleList list; 397 try { 398 evaluator.setNonEmpty(false); 399 400 // Construct an array containing the value of the expression 401 // for each member. 402 403 list = listCalc.evaluateList(evaluator); 404 assert list != null; 405 if (list.isEmpty()) { 406 return list.getArity() == 1 407 ? new MemberSortResult( 408 new Object[0], 409 Collections.<Member, Integer>emptyMap()) 410 : new TupleSortResult( 411 new Object[0], 412 Collections.<List<Member>, Integer>emptyMap()); 413 } 414 415 if (list.getArity() == 1) { 416 memberValueMap = new HashMap<Member, Object>(); 417 tupleValueMap = null; 418 for (Member member : list.slice(0)) { 419 evaluator.setContext(member); 420 final Object keyValue = keyCalc.evaluate(evaluator); 421 if (keyValue instanceof RuntimeException) { 422 if (exception == null) { 423 exception = (RuntimeException) keyValue; 424 } 425 } else if (Util.isNull(keyValue)) { 426 // nothing to do 427 } else { 428 // Assume it's the first time seeing this keyValue. 429 Integer valueCounter = 430 uniqueValueCounterMap.put(keyValue, ONE); 431 if (valueCounter != null) { 432 // Update the counter on how many times this 433 // keyValue has been seen. 434 uniqueValueCounterMap.put( 435 keyValue, valueCounter + 1); 436 } 437 memberValueMap.put(member, keyValue); 438 } 439 } 440 numValues = memberValueMap.keySet().size(); 441 } else { 442 tupleValueMap = new HashMap<List<Member>, Object>(); 443 memberValueMap = null; 444 for (List<Member> tuple : list) { 445 evaluator.setContext(tuple); 446 final Object keyValue = keyCalc.evaluate(evaluator); 447 if (keyValue instanceof RuntimeException) { 448 if (exception == null) { 449 exception = (RuntimeException) keyValue; 450 } 451 } else if (Util.isNull(keyValue)) { 452 // nothing to do 453 } else { 454 // Assume it's the first time seeing this keyValue. 455 Integer valueCounter = uniqueValueCounterMap.put( 456 keyValue, ONE); 457 if (valueCounter != null) { 458 // Update the counter on how many times this 459 // keyValue has been seen. 460 uniqueValueCounterMap.put( 461 keyValue, valueCounter + 1); 462 } 463 tupleValueMap.put(tuple, keyValue); 464 } 465 } 466 numValues = tupleValueMap.keySet().size(); 467 } 468 } finally { 469 evaluator.restore(savepoint); 470 } 471 472 473 // If there were exceptions, quit now... we'll be back. 474 if (exception != null) { 475 return exception; 476 } 477 478 final Object[] allValuesSorted = new Object[numValues]; 479 480 // Now build the sorted array containing all keyValues 481 // And update the counter to the rank 482 int currentOrdinal = 0; 483 //noinspection unchecked 484 final Map<Object, Integer> uniqueValueRankMap = 485 new TreeMap<Object, Integer>( 486 Collections.<Object>reverseOrder()); 487 488 for (Map.Entry<Object, Integer> entry 489 : uniqueValueCounterMap.entrySet()) 490 { 491 Object keyValue = entry.getKey(); 492 Integer valueCount = entry.getValue(); 493 // Because uniqueValueCounterMap is already sorted, so the 494 // reconstructed allValuesSorted is guaranteed to be sorted. 495 for (int i = 0; i < valueCount; i ++) { 496 allValuesSorted[currentOrdinal + i] = keyValue; 497 } 498 uniqueValueRankMap.put(keyValue, currentOrdinal + 1); 499 currentOrdinal += valueCount; 500 } 501 502 // Build a member/tuple to rank map 503 if (list.getArity() == 1) { 504 final Map<Member, Integer> rankMap = 505 new HashMap<Member, Integer>(); 506 for (Map.Entry<Member, Object> entry 507 : memberValueMap.entrySet()) 508 { 509 int oneBasedRank = 510 uniqueValueRankMap.get(entry.getValue()); 511 rankMap.put(entry.getKey(), oneBasedRank); 512 } 513 return new MemberSortResult(allValuesSorted, rankMap); 514 } else { 515 final Map<List<Member>, Integer> rankMap = 516 new HashMap<List<Member>, Integer>(); 517 for (Map.Entry<List<Member>, Object> entry 518 : tupleValueMap.entrySet()) 519 { 520 int oneBasedRank = 521 uniqueValueRankMap.get(entry.getValue()); 522 rankMap.put(entry.getKey(), oneBasedRank); 523 } 524 return new TupleSortResult(allValuesSorted, rankMap); 525 } 526 } 527 } 528 529 /** 530 * Holder for the result of sorting a set of values. 531 * It provides simple interface to look up the rank for a member or a tuple. 532 */ 533 private static abstract class SortResult { 534 /** 535 * All values in sorted order; Duplicates are not removed. 536 * E.g. Set (15,15,5,0) 537 * 10 should be ranked 3. 538 * 539 * <p>Null values are not present: they would be at the end, anyway. 540 */ 541 final Object[] values; 542 543 544 public SortResult(Object[] values) { 545 this.values = values; 546 } 547 548 public boolean isEmpty() { 549 return values == null; 550 } 551 552 public void print(PrintWriter pw) { 553 if (values == null) { 554 pw.println("SortResult: empty"); 555 } else { 556 pw.println("SortResult {"); 557 for (int i = 0; i < values.length; i++) { 558 if (i > 0) { 559 pw.println(","); 560 } 561 Object value = values[i]; 562 pw.print(value); 563 } 564 pw.println("}"); 565 } 566 pw.flush(); 567 } 568 } 569 570 private static class MemberSortResult extends SortResult { 571 /** 572 * The precomputed rank associated with all members 573 */ 574 final Map<Member, Integer> rankMap; 575 576 public MemberSortResult( 577 Object[] values, Map<Member, Integer> rankMap) 578 { 579 super(values); 580 this.rankMap = rankMap; 581 } 582 583 public Integer rankOf(Member member) { 584 return rankMap.get(member); 585 } 586 } 587 588 private static class TupleSortResult extends SortResult { 589 /** 590 * The precomputed rank associated with all tuples 591 */ 592 final Map<List<Member>, Integer> rankMap; 593 594 public TupleSortResult( 595 Object[] values, Map<List<Member>, Integer> rankMap) 596 { 597 super(values); 598 this.rankMap = rankMap; 599 } 600 601 public Integer rankOf(Member[] tuple) { 602 return rankMap.get(Arrays.asList(tuple)); 603 } 604 } 605 606 /** 607 * Expression which evaluates an expression to form a list of tuples. 608 * 609 * <p>The result is a value of type 610 * {@link mondrian.olap.fun.RankFunDef.RankedMemberList} or 611 * {@link mondrian.olap.fun.RankFunDef.RankedTupleList}, or 612 * null if the list is empty. 613 */ 614 private static class RankedListCalc extends AbstractCalc { 615 private final ListCalc listCalc; 616 private final boolean tuple; 617 618 /** 619 * Creates a RankedListCalc. 620 * 621 * @param listCalc Compiled expression to compute the list 622 * @param tuple Whether elements of the list are tuples (as opposed to 623 * members) 624 */ 625 public RankedListCalc(ListCalc listCalc, boolean tuple) { 626 super(new DummyExp(listCalc.getType()), new Calc[] {listCalc}); 627 this.listCalc = listCalc; 628 this.tuple = tuple; 629 } 630 631 public Object evaluate(Evaluator evaluator) { 632 // Construct an array containing the value of the expression 633 // for each member. 634 TupleList tupleList = listCalc.evaluateList(evaluator); 635 assert tupleList != null; 636 if (tuple) { 637 return new RankedTupleList(tupleList); 638 } else { 639 return new RankedMemberList(tupleList.slice(0)); 640 } 641 } 642 } 643 644 /** 645 * Data structure which contains a list and can return the position of an 646 * element in the list in O(log N). 647 */ 648 static class RankedMemberList { 649 Map<Member, Integer> map = new HashMap<Member, Integer>(); 650 651 RankedMemberList(List<Member> members) { 652 int i = -1; 653 for (final Member member : members) { 654 ++i; 655 final Integer value = map.put(member, i); 656 if (value != null) { 657 // The list already contained a value for this key -- put 658 // it back. 659 map.put(member, value); 660 } 661 } 662 } 663 664 int indexOf(Member m) { 665 Integer integer = map.get(m); 666 if (integer == null) { 667 return -1; 668 } else { 669 return integer; 670 } 671 } 672 } 673 /** 674 * Data structure which contains a list and can return the position of an 675 * element in the list in O(log N). 676 */ 677 static class RankedTupleList { 678 final Map<List<Member>, Integer> map = 679 new HashMap<List<Member>, Integer>(); 680 681 RankedTupleList(TupleList tupleList) { 682 int i = -1; 683 for (final List<Member> tupleMembers : tupleList.fix()) { 684 ++i; 685 final Integer value = map.put(tupleMembers, i); 686 if (value != null) { 687 // The list already contained a value for this key -- put 688 // it back. 689 map.put(tupleMembers, value); 690 } 691 } 692 } 693 694 int indexOf(List<Member> tupleMembers) { 695 Integer integer = map.get(tupleMembers); 696 if (integer == null) { 697 return -1; 698 } else { 699 return integer; 700 } 701 } 702 } 703} 704 705// End RankFunDef.java