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) 2001-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.calc.Calc; 014import mondrian.mdx.ResolvedFunCall; 015import mondrian.olap.*; 016import mondrian.olap.fun.AggregateFunDef; 017import mondrian.olap.fun.VisualTotalsFunDef; 018import mondrian.server.Locus; 019import mondrian.spi.PropertyFormatter; 020import mondrian.util.*; 021 022import org.apache.commons.collections.map.Flat3Map; 023import org.apache.log4j.Logger; 024 025import org.eigenbase.util.property.StringProperty; 026 027import java.math.BigDecimal; 028import java.util.*; 029 030 031/** 032 * Basic implementation of a member in a {@link RolapHierarchy}. 033 * 034 * @author jhyde 035 * @since 10 August, 2001 036 */ 037public class RolapMemberBase 038 extends MemberBase 039 implements RolapMember 040{ 041 /** 042 * For members of a level with an ordinal expression defined, the 043 * value of that expression for this member as retrieved via JDBC; 044 * otherwise null. 045 */ 046 private Comparable orderKey; 047 private Boolean isParentChildLeaf; 048 private static final Logger LOGGER = Logger.getLogger(RolapMember.class); 049 050 /** 051 * Sets a member's parent. 052 * 053 * <p>Can screw up the caching structure. Only to be called by 054 * {@link mondrian.olap.CacheControl#createMoveCommand}. 055 * 056 * <p>New parent must be in same level as old parent. 057 * 058 * @param parentMember New parent member 059 * 060 * @see #getParentMember() 061 * @see #getParentUniqueName() 062 */ 063 void setParentMember(RolapMember parentMember) { 064 final RolapMember previousParentMember = getParentMember(); 065 if (previousParentMember.getLevel() != parentMember.getLevel()) { 066 throw new IllegalArgumentException( 067 "new parent belongs to different level than old"); 068 } 069 this.parentMember = parentMember; 070 } 071 072 /** Ordinal of the member within the hierarchy. Some member readers do not 073 * use this property; in which case, they should leave it as its default, 074 * -1. */ 075 private int ordinal; 076 private final Object key; 077 078 /** 079 * Maps property name to property value. 080 * 081 * <p> We expect there to be a lot of members, but few of them will 082 * have properties. So to reduce memory usage, when empty, this is set to 083 * an immutable empty set. 084 */ 085 private Map<String, Object> mapPropertyNameToValue; 086 087 private Boolean containsAggregateFunction = null; 088 089 /** 090 * Creates a RolapMemberBase. 091 * 092 * @param parentMember Parent member 093 * @param level Level this member belongs to 094 * @param key Key to this member in the underlying RDBMS 095 * @param name Name of this member 096 * @param memberType Type of member 097 */ 098 protected RolapMemberBase( 099 RolapMember parentMember, 100 RolapLevel level, 101 Object key, 102 String name, 103 MemberType memberType) 104 { 105 super(parentMember, level, memberType); 106 assert key != null; 107 assert !(parentMember instanceof RolapCubeMember) 108 || this instanceof RolapCalculatedMember 109 || this instanceof VisualTotalsFunDef.VisualTotalMember; 110 if (key instanceof byte[]) { 111 // Some drivers (e.g. Derby) return byte arrays for binary columns 112 // but byte arrays do not implement Comparable 113 this.key = new String((byte[])key); 114 } else { 115 this.key = key; 116 } 117 this.ordinal = -1; 118 this.mapPropertyNameToValue = Collections.emptyMap(); 119 120 if (name != null 121 && !(key != null && name.equals(key.toString()))) 122 { 123 // Save memory by only saving the name as a property if it's 124 // different from the key. 125 setProperty(Property.NAME.name, name); 126 } else if (key != null) { 127 setUniqueName(key); 128 } 129 } 130 131 protected RolapMemberBase() { 132 super(); 133 this.key = RolapUtil.sqlNullValue; 134 } 135 136 RolapMemberBase(RolapMember parentMember, RolapLevel level, Object value) { 137 this(parentMember, level, value, null, MemberType.REGULAR); 138 assert !(level instanceof RolapCubeLevel); 139 } 140 141 protected Logger getLogger() { 142 return LOGGER; 143 } 144 145 public RolapLevel getLevel() { 146 return (RolapLevel) level; 147 } 148 149 public RolapHierarchy getHierarchy() { 150 return (RolapHierarchy) level.getHierarchy(); 151 } 152 153 public RolapMember getParentMember() { 154 return (RolapMember) super.getParentMember(); 155 } 156 157 // Regular members do not have annotations. Measures and calculated members 158 // do, so they override this method. 159 public Map<String, Annotation> getAnnotationMap() { 160 return Collections.emptyMap(); 161 } 162 163 public int hashCode() { 164 return getUniqueName().hashCode(); 165 } 166 167 public boolean equals(Object o) { 168 if (o == this) { 169 return true; 170 } 171 if (o instanceof RolapMemberBase && equals((RolapMemberBase) o)) { 172 return true; 173 } 174 if (o instanceof RolapCubeMember 175 && equals(((RolapCubeMember) o).getRolapMember())) 176 { 177 // TODO: remove, RolapCubeMember should never meet RolapMember 178 assert !Bug.BugSegregateRolapCubeMemberFixed; 179 return true; 180 } 181 return false; 182 } 183 184 public boolean equals(OlapElement o) { 185 return (o instanceof RolapMemberBase) 186 && equals((RolapMemberBase) o); 187 } 188 189 private boolean equals(RolapMemberBase that) { 190 assert that != null; // public method should have checked 191 // Do not use equalsIgnoreCase; unique names should be identical, and 192 // hashCode assumes this. 193 return this.getUniqueName().equals(that.getUniqueName()); 194 } 195 196 void makeUniqueName(HierarchyUsage hierarchyUsage) { 197 if (parentMember == null && key != null) { 198 String n = hierarchyUsage.getName(); 199 if (n != null) { 200 String name = keyToString(key); 201 n = Util.quoteMdxIdentifier(n); 202 this.uniqueName = Util.makeFqName(n, name); 203 if (getLogger().isDebugEnabled()) { 204 getLogger().debug( 205 "RolapMember.makeUniqueName: uniqueName=" + uniqueName); 206 } 207 } 208 } 209 } 210 211 protected void setUniqueName(Object key) { 212 String name = keyToString(key); 213 214 // Drop the '[All Xxxx]' segment in regular members. 215 // Keep the '[All Xxxx]' segment in the 'all' member. 216 // Keep the '[All Xxxx]' segment in calc members. 217 // Drop it in visual-totals and parent-child members (which are flagged 218 // as calculated, but do have a data member). 219 if (parentMember == null 220 || (parentMember.isAll() 221 && (!isCalculated() 222 || this instanceof VisualTotalsFunDef.VisualTotalMember 223 || getDataMember() != null))) 224 { 225 final RolapHierarchy hierarchy = getHierarchy(); 226 final Dimension dimension = hierarchy.getDimension(); 227 final RolapLevel level = getLevel(); 228 if (dimension.getDimensionType() != null 229 && (dimension.getDimensionType().equals( 230 DimensionType.MeasuresDimension) 231 && hierarchy.getName().equals(dimension.getName()))) 232 { 233 // Kludge to ensure that calc members are called 234 // [Measures].[Foo] not [Measures].[Measures].[Foo]. We can 235 // remove this code when we revisit the scheme to generate 236 // member unique names. 237 this.uniqueName = Util.makeFqName(dimension, name); 238 } else { 239 if (name.equals(level.getName())) { 240 this.uniqueName = 241 Util.makeFqName( 242 Util.makeFqName( 243 hierarchy.getUniqueName(), 244 level.getName()), 245 name); 246 } else { 247 this.uniqueName = Util.makeFqName(hierarchy, name); 248 } 249 } 250 } else { 251 this.uniqueName = Util.makeFqName(parentMember, name); 252 } 253 } 254 255 public boolean isCalculatedInQuery() { 256 return false; 257 } 258 259 public String getName() { 260 final Object name = 261 getPropertyValue(Property.NAME.name); 262 return (name != null) 263 ? String.valueOf(name) 264 : keyToString(key); 265 } 266 267 public void setName(String name) { 268 throw new Error("unsupported"); 269 } 270 271 /** 272 * Sets a property of this member to a given value. 273 * 274 * <p>WARNING: Setting system properties such as "$name" may have nasty 275 * side-effects. 276 */ 277 public synchronized void setProperty(String name, Object value) { 278 if (name.equals(Property.CAPTION.name)) { 279 setCaption((String)value); 280 return; 281 } 282 283 if (mapPropertyNameToValue.isEmpty()) { 284 // the empty map is shared and immutable; create our own 285 PropertyValueMapFactory factory = 286 PropertyValueMapFactoryFactory.getPropertyValueMapFactory(); 287 mapPropertyNameToValue = factory.create(this); 288 } 289 if (name.equals(Property.NAME.name)) { 290 if (value == null) { 291 value = RolapUtil.mdxNullLiteral(); 292 } 293 setUniqueName(value); 294 } 295 296 if (name.equals(Property.MEMBER_ORDINAL.name)) { 297 String ordinal = (String) value; 298 if (ordinal.startsWith("\"") && ordinal.endsWith("\"")) { 299 ordinal = ordinal.substring(1, ordinal.length() - 1); 300 } 301 final double d = Double.parseDouble(ordinal); 302 setOrdinal((int) d); 303 } 304 305 mapPropertyNameToValue.put(name, value); 306 } 307 308 public Object getPropertyValue(String propertyName) { 309 return getPropertyValue(propertyName, true); 310 } 311 312 public Object getPropertyValue(String propertyName, boolean matchCase) { 313 Property property = Property.lookup(propertyName, matchCase); 314 if (property != null) { 315 Schema schema; 316 Member parentMember; 317 List<RolapMember> list; 318 switch (property.ordinal) { 319 case Property.NAME_ORDINAL: 320 // Do NOT call getName() here. This property is internal, 321 // and must fall through to look in the property list. 322 break; 323 324 case Property.CAPTION_ORDINAL: 325 return getCaption(); 326 327 case Property.CONTRIBUTING_CHILDREN_ORDINAL: 328 list = new ArrayList<RolapMember>(); 329 getHierarchy().getMemberReader().getMemberChildren(this, list); 330 return list; 331 332 case Property.CATALOG_NAME_ORDINAL: 333 // TODO: can't go from member to connection thence to 334 // Connection.getCatalogName() 335 break; 336 337 case Property.SCHEMA_NAME_ORDINAL: 338 schema = getHierarchy().getDimension().getSchema(); 339 return schema.getName(); 340 341 case Property.CUBE_NAME_ORDINAL: 342 // TODO: can't go from member to cube cube yet 343 break; 344 345 case Property.DIMENSION_UNIQUE_NAME_ORDINAL: 346 return getHierarchy().getDimension().getUniqueName(); 347 348 case Property.HIERARCHY_UNIQUE_NAME_ORDINAL: 349 return getHierarchy().getUniqueName(); 350 351 case Property.LEVEL_UNIQUE_NAME_ORDINAL: 352 return getLevel().getUniqueName(); 353 354 case Property.LEVEL_NUMBER_ORDINAL: 355 return getLevel().getDepth(); 356 357 case Property.MEMBER_UNIQUE_NAME_ORDINAL: 358 return getUniqueName(); 359 360 case Property.MEMBER_NAME_ORDINAL: 361 return getName(); 362 363 case Property.MEMBER_TYPE_ORDINAL: 364 return getMemberType().ordinal(); 365 366 case Property.MEMBER_GUID_ORDINAL: 367 return null; 368 369 case Property.MEMBER_CAPTION_ORDINAL: 370 return getCaption(); 371 372 case Property.MEMBER_ORDINAL_ORDINAL: 373 return getOrdinal(); 374 375 case Property.CHILDREN_CARDINALITY_ORDINAL: 376 return Locus.execute( 377 ((RolapSchema) level.getDimension().getSchema()) 378 .getInternalConnection(), 379 "Member.CHILDREN_CARDINALITY", 380 new Locus.Action<Integer>() { 381 public Integer execute() { 382 if (isAll() && childLevelHasApproxRowCount()) { 383 return getLevel().getChildLevel() 384 .getApproxRowCount(); 385 } else { 386 ArrayList<RolapMember> list = 387 new ArrayList<RolapMember>(); 388 getHierarchy().getMemberReader() 389 .getMemberChildren( 390 RolapMemberBase.this, list); 391 return list.size(); 392 } 393 } 394 } 395 ); 396 397 case Property.PARENT_LEVEL_ORDINAL: 398 parentMember = getParentMember(); 399 return parentMember == null 400 ? 0 401 : parentMember.getLevel().getDepth(); 402 403 case Property.PARENT_UNIQUE_NAME_ORDINAL: 404 parentMember = getParentMember(); 405 return parentMember == null 406 ? null 407 : parentMember.getUniqueName(); 408 409 case Property.PARENT_COUNT_ORDINAL: 410 parentMember = getParentMember(); 411 return parentMember == null ? 0 : 1; 412 413 case Property.VISIBLE_ORDINAL: 414 break; 415 416 case Property.MEMBER_KEY_ORDINAL: 417 case Property.KEY_ORDINAL: 418 return this == this.getHierarchy().getAllMember() 419 ? 0 420 : getKey(); 421 422 case Property.SCENARIO_ORDINAL: 423 return ScenarioImpl.forMember(this); 424 425 default: 426 break; 427 // fall through 428 } 429 } 430 return getPropertyFromMap(propertyName, matchCase); 431 } 432 433 /** 434 * Returns the value of a property by looking it up in the property map. 435 * 436 * @param propertyName Name of property 437 * @param matchCase Whether to match name case-sensitive 438 * @return Property value 439 */ 440 protected Object getPropertyFromMap( 441 String propertyName, 442 boolean matchCase) 443 { 444 synchronized (this) { 445 if (matchCase) { 446 return mapPropertyNameToValue.get(propertyName); 447 } else { 448 for (String key : mapPropertyNameToValue.keySet()) { 449 if (key.equalsIgnoreCase(propertyName)) { 450 return mapPropertyNameToValue.get(key); 451 } 452 } 453 return null; 454 } 455 } 456 } 457 458 protected boolean childLevelHasApproxRowCount() { 459 return getLevel().getChildLevel().getApproxRowCount() 460 > Integer.MIN_VALUE; 461 } 462 463 /** 464 * @deprecated Use {@link #isAll}; will be removed in mondrian-4.0 465 */ 466 public boolean isAllMember() { 467 return getLevel().getHierarchy().hasAll() 468 && getLevel().getDepth() == 0; 469 } 470 471 public Property[] getProperties() { 472 return getLevel().getInheritedProperties(); 473 } 474 475 public int getOrdinal() { 476 return ordinal; 477 } 478 479 public Comparable getOrderKey() { 480 return orderKey; 481 } 482 483 void setOrdinal(int ordinal) { 484 if (this.ordinal == -1) { 485 this.ordinal = ordinal; 486 } 487 } 488 489 void setOrderKey(Comparable orderKey) { 490 this.orderKey = orderKey; 491 } 492 493 private void resetOrdinal() { 494 this.ordinal = -1; 495 } 496 497 public Object getKey() { 498 return this.key; 499 } 500 501 /** 502 * Compares this member to another {@link RolapMemberBase}. 503 * 504 * <p>The method first compares on keys; null keys always collate last. 505 * If the keys are equal, it compares using unique name. 506 * 507 * <p>This method does not consider {@link #ordinal} field, because 508 * ordinal is only unique within a parent. If you want to compare 509 * members which may be at any position in the hierarchy, use 510 * {@link mondrian.olap.fun.FunUtil#compareHierarchically}. 511 * 512 * @return -1 if this is less, 0 if this is the same, 1 if this is greater 513 */ 514 public int compareTo(Object o) { 515 RolapMemberBase other = (RolapMemberBase)o; 516 assert this.key != null && other.key != null; 517 518 if (this.key == RolapUtil.sqlNullValue 519 && other.key == RolapUtil.sqlNullValue) 520 { 521 // if both keys are null, they are equal. 522 // compare by unique name. 523 return this.getName().compareTo(other.getName()); 524 } 525 526 if (other.key == RolapUtil.sqlNullValue) { 527 // not null is greater than null 528 return 1; 529 } 530 531 if (this.key == RolapUtil.sqlNullValue) { 532 // null is less than not null 533 return -1; 534 } 535 536 // as both keys are not null, compare by key 537 // String, Double, Integer should be possible 538 // any key object should be "Comparable" 539 // anyway - keys should be of the same class 540 if (this.key.getClass().equals(other.key.getClass())) { 541 if (this.key instanceof String) { 542 // use a special case sensitive compare name which 543 // first compares w/o case, and if 0 compares with case 544 return Util.caseSensitiveCompareName( 545 (String) this.key, (String) other.key); 546 } else { 547 return Util.compareKey(this.key, other.key); 548 } 549 } 550 // Compare by unique name in case of different key classes. 551 // This is possible, if a new calculated member is created 552 // in a dimension with an Integer key. The calculated member 553 // has a key of type String. 554 return this.getUniqueName().compareTo(other.getUniqueName()); 555 } 556 557 public boolean isHidden() { 558 final RolapLevel rolapLevel = getLevel(); 559 switch (rolapLevel.getHideMemberCondition()) { 560 case Never: 561 return false; 562 563 case IfBlankName: 564 { 565 // If the key value in the database is null, then we use 566 // a special key value whose toString() is "null". 567 final String name = getName(); 568 return name.equals(RolapUtil.mdxNullLiteral()) 569 || Util.isBlank(name); 570 } 571 572 case IfParentsName: 573 { 574 final Member parentMember = getParentMember(); 575 if (parentMember == null) { 576 return false; 577 } 578 final String parentName = parentMember.getName(); 579 final String name = getName(); 580 return (parentName == null ? "" : parentName).equals( 581 name == null ? "" : name); 582 } 583 584 default: 585 throw Util.badValue(rolapLevel.getHideMemberCondition()); 586 } 587 } 588 589 public int getDepth() { 590 return getLevel().getDepth(); 591 } 592 593 public String getPropertyFormattedValue(String propertyName) { 594 // do we have a formatter ? if yes, use it 595 Property[] props = getLevel().getProperties(); 596 Property prop = null; 597 for (Property prop1 : props) { 598 if (prop1.getName().equals(propertyName)) { 599 prop = prop1; 600 break; 601 } 602 } 603 PropertyFormatter pf; 604 if (prop != null && (pf = prop.getFormatter()) != null) { 605 return pf.formatProperty( 606 this, propertyName, 607 getPropertyValue(propertyName)); 608 } 609 610 Object val = getPropertyValue(propertyName); 611 612 if (val != null && val instanceof Number) { 613 // Numbers are a special case. We don't want any 614 // scientific notations, so we wrap in a BigDecimal 615 // before calling toString. This is cheap to perform here 616 // because this method only gets called by the GUI. 617 val = new BigDecimal(((Number)val).doubleValue()); 618 } 619 620 return (val == null) 621 ? "" 622 : val.toString(); 623 } 624 625 public boolean isParentChildLeaf() { 626 if (isParentChildLeaf == null) { 627 isParentChildLeaf = getLevel().isParentChild() 628 && getDimension().getSchema().getSchemaReader() 629 .getMemberChildren(this).size() == 0; 630 } 631 return isParentChildLeaf; 632 } 633 634 /** 635 * Returns a list of member lists where the first member 636 * list is the root members while the last member array is the 637 * leaf members. 638 * 639 * <p>If you know that you will need to get all or most of the members of 640 * a hierarchy, then calling this which gets all of the hierarchy's 641 * members all at once is much faster than getting members one at 642 * a time. 643 * 644 * @param schemaReader Schema reader 645 * @param hierarchy Hierarchy 646 * @return List of arrays of members 647 */ 648 public static List<List<Member>> getAllMembers( 649 SchemaReader schemaReader, 650 Hierarchy hierarchy) 651 { 652 long start = System.currentTimeMillis(); 653 654 try { 655 // Getting the members by Level is the fastest way that I could 656 // find for getting all of a hierarchy's members. 657 List<List<Member>> list = new ArrayList<List<Member>>(); 658 Level[] levels = hierarchy.getLevels(); 659 for (Level level : levels) { 660 List<Member> members = 661 schemaReader.getLevelMembers(level, true); 662 if (members != null) { 663 list.add(members); 664 } 665 } 666 return list; 667 } finally { 668 if (LOGGER.isDebugEnabled()) { 669 long end = System.currentTimeMillis(); 670 LOGGER.debug( 671 "RolapMember.getAllMembers: time=" + (end - start)); 672 } 673 } 674 } 675 676 public static int getHierarchyCardinality( 677 SchemaReader schemaReader, 678 Hierarchy hierarchy) 679 { 680 int cardinality = 0; 681 Level[] levels = hierarchy.getLevels(); 682 for (Level level1 : levels) { 683 cardinality += schemaReader.getLevelCardinality(level1, true, true); 684 } 685 return cardinality; 686 } 687 688 /** 689 * Sets member ordinal values using a Bottom-up/Top-down algorithm. 690 * 691 * <p>Gets an array of members for each level and traverses 692 * array for the lowest level, setting each member's 693 * parent's parent's etc. member's ordinal if not set working back 694 * down to the leaf member and then going to the next leaf member 695 * and traversing up again. 696 * 697 * <p>The above algorithm only works for a hierarchy that has all of its 698 * leaf members in the same level (that is, a non-ragged hierarchy), which 699 * is the norm. After all member ordinal values have been set, traverses 700 * the array of members, making sure that all members' ordinals have been 701 * set. If one is found that is not set, then one must to a full Top-down 702 * setting of the ordinals. 703 * 704 * <p>The Bottom-up/Top-down algorithm is MUCH faster than the Top-down 705 * algorithm. 706 * 707 * @param schemaReader Schema reader 708 * @param seedMember Member 709 */ 710 public static void setOrdinals( 711 SchemaReader schemaReader, 712 Member seedMember) 713 { 714 seedMember = RolapUtil.strip((RolapMember) seedMember); 715 716 // The following are times for executing different set ordinals 717 // algorithms for both the FoodMart Sales cube/Store dimension 718 // and a Large Data set with a dimension with about 250,000 members. 719 // 720 // Times: 721 // Original setOrdinals Top-down 722 // Foodmart: 63ms 723 // Large Data set: 651865ms 724 // Calling getAllMembers before calling original setOrdinals 725 // Top-down 726 // Foodmart: 32ms 727 // Large Data set: 73880ms 728 // Bottom-up/Top-down 729 // Foodmart: 17ms 730 // Large Data set: 4241ms 731 long start = System.currentTimeMillis(); 732 733 try { 734 Hierarchy hierarchy = seedMember.getHierarchy(); 735 int ordinal = hierarchy.hasAll() ? 1 : 0; 736 List<List<Member>> levelMembers = 737 getAllMembers(schemaReader, hierarchy); 738 List<Member> leafMembers = 739 levelMembers.get(levelMembers.size() - 1); 740 levelMembers = levelMembers.subList(0, levelMembers.size() - 1); 741 742 // Set all ordinals 743 for (Member child : leafMembers) { 744 ordinal = bottomUpSetParentOrdinals(ordinal, child); 745 ordinal = setOrdinal(child, ordinal); 746 } 747 748 boolean needsFullTopDown = needsFullTopDown(levelMembers); 749 750 // If we must to a full Top-down, then first reset all ordinal 751 // values to -1, and then call the Top-down 752 if (needsFullTopDown) { 753 for (List<Member> members : levelMembers) { 754 for (Member member : members) { 755 if (member instanceof RolapMemberBase) { 756 ((RolapMemberBase) member).resetOrdinal(); 757 } 758 } 759 } 760 761 // call full Top-down 762 setOrdinalsTopDown(schemaReader, seedMember); 763 } 764 } finally { 765 if (LOGGER.isDebugEnabled()) { 766 long end = System.currentTimeMillis(); 767 LOGGER.debug("RolapMember.setOrdinals: time=" + (end - start)); 768 } 769 } 770 } 771 772 /** 773 * Returns whether the ordinal assignment algorithm needs to perform 774 * the more expensive top-down algorithm. If the hierarchy is 'uneven', not 775 * all leaf members are at the same level, then bottom-up setting of 776 * ordinals will have missed some. 777 * 778 * @param levelMembers Array containing the list of members in each level 779 * except the leaf level 780 * @return whether we need to apply the top-down ordinal assignment 781 */ 782 private static boolean needsFullTopDown(List<List<Member>> levelMembers) { 783 for (List<Member> members : levelMembers) { 784 for (Member member : members) { 785 if (member.getOrdinal() == -1) { 786 return true; 787 } 788 } 789 } 790 return false; 791 } 792 793 /** 794 * Walks up the hierarchy, setting the ordinals of ancestors until it 795 * reaches the root or hits an ancestor whose ordinal has already been 796 * assigned. 797 * 798 * <p>Assigns the given ordinal to the ancestor nearest the root which has 799 * not been assigned an ordinal, and increments by one for each descendant. 800 * 801 * @param ordinal Ordinal to assign to deepest ancestor 802 * @param child Member whose ancestors ordinals to set 803 * @return Ordinal, incremented for each time it was used 804 */ 805 private static int bottomUpSetParentOrdinals(int ordinal, Member child) { 806 Member parent = child.getParentMember(); 807 if ((parent != null) && parent.getOrdinal() == -1) { 808 ordinal = bottomUpSetParentOrdinals(ordinal, parent); 809 ordinal = setOrdinal(parent, ordinal); 810 } 811 return ordinal; 812 } 813 814 private static int setOrdinal(Member member, int ordinal) { 815 if (member instanceof RolapMemberBase) { 816 ((RolapMemberBase) member).setOrdinal(ordinal++); 817 } else { 818 // TODO 819 LOGGER.warn( 820 "RolapMember.setAllChildren: NOT RolapMember " 821 + "member.name=" + member.getName() 822 + ", member.class=" + member.getClass().getName() 823 + ", ordinal=" + ordinal); 824 ordinal++; 825 } 826 return ordinal; 827 } 828 829 /** 830 * Sets ordinals of a complete member hierarchy as required by the 831 * MEMBER_ORDINAL XMLA element using a depth-first algorithm. 832 * 833 * <p>For big hierarchies it takes a bunch of time. SQL Server is 834 * relatively fast in comparison so it might be storing such 835 * information in the DB. 836 * 837 * @param schemaReader Schema reader 838 * @param member Member 839 */ 840 private static void setOrdinalsTopDown( 841 SchemaReader schemaReader, 842 Member member) 843 { 844 long start = System.currentTimeMillis(); 845 846 try { 847 Member parent = schemaReader.getMemberParent(member); 848 849 if (parent == null) { 850 // top of the world 851 int ordinal = 0; 852 853 List<Member> siblings = 854 schemaReader.getHierarchyRootMembers(member.getHierarchy()); 855 856 for (Member sibling : siblings) { 857 ordinal = setAllChildren(ordinal, schemaReader, sibling); 858 } 859 860 } else { 861 setOrdinalsTopDown(schemaReader, parent); 862 } 863 } finally { 864 if (LOGGER.isDebugEnabled()) { 865 long end = System.currentTimeMillis(); 866 LOGGER.debug( 867 "RolapMember.setOrdinalsTopDown: time=" + (end - start)); 868 } 869 } 870 } 871 872 private static int setAllChildren( 873 int ordinal, 874 SchemaReader schemaReader, 875 Member member) 876 { 877 ordinal = setOrdinal(member, ordinal); 878 879 List<Member> children = schemaReader.getMemberChildren(member); 880 for (Member child : children) { 881 ordinal = setAllChildren(ordinal, schemaReader, child); 882 } 883 884 return ordinal; 885 } 886 887 /** 888 * Converts a key to a string to be used as part of the member's name 889 * and unique name. 890 * 891 * <p>Usually, it just calls {@link Object#toString}. But if the key is an 892 * integer value represented in a floating-point column, we'd prefer the 893 * integer value. For example, one member of the 894 * <code>[Sales].[Store SQFT]</code> dimension comes out "20319.0" but we'd 895 * like it to be "20319". 896 */ 897 protected static String keyToString(Object key) { 898 String name; 899 if (key == null || RolapUtil.sqlNullValue.equals(key)) { 900 name = RolapUtil.mdxNullLiteral(); 901 } else if (key instanceof Id.NameSegment) { 902 name = ((Id.NameSegment) key).name; 903 } else { 904 name = key.toString(); 905 } 906 if ((key instanceof Number) && name.endsWith(".0")) { 907 name = name.substring(0, name.length() - 2); 908 } 909 return name; 910 } 911 912 /** 913 * <p>Interface definition for the pluggable factory used to decide 914 * which implementation of {@link java.util.Map} to use to store 915 * property string/value pairs for member properties.</p> 916 * 917 * <p>This permits tuning for performance, memory allocation, etcetera. 918 * For example, if a member belongs to a level which has 10 member 919 * properties a HashMap may be preferred, while if the level has 920 * only two member properties a Flat3Map may make more sense.</p> 921 */ 922 public interface PropertyValueMapFactory { 923 /** 924 * Creates a {@link java.util.Map} to be used for storing 925 * property string/value pairs for the specified 926 * {@link mondrian.olap.Member}. 927 * 928 * @param member Member 929 * @return the Map instance to store property/value pairs 930 */ 931 Map<String, Object> create(Member member); 932 } 933 934 /** 935 * Default {@link RolapMemberBase.PropertyValueMapFactory} 936 * implementation, used if 937 * {@link mondrian.olap.MondrianProperties#PropertyValueMapFactoryClass} 938 * is not set. 939 */ 940 public static final class DefaultPropertyValueMapFactory 941 implements PropertyValueMapFactory 942 { 943 /** 944 * {@inheritDoc} 945 * <p>This factory creates an 946 * {@link org.apache.commons.collections.map.Flat3Map} if 947 * it appears that the provided member has less than 3 properties, 948 * and a {@link java.util.HashMap} if it appears 949 * that it has more than 3.</p> 950 * 951 * <p>Guessing the number of properties 952 * can be tricky since some subclasses of 953 * {@link mondrian.olap.Member}</p> have additional properties 954 * that aren't explicitly declared. The most common offenders 955 * are the (@link mondrian.olap.Measure} implementations, which 956 * often have 4 or more undeclared properties, so if the member 957 * is a measure, the factory will create a {@link java.util.HashMap}. 958 * </p> 959 * 960 * @param member {@inheritDoc} 961 * @return {@inheritDoc} 962 */ 963 @SuppressWarnings({"unchecked"}) 964 public Map<String, Object> create(Member member) { 965 assert member != null; 966 Property[] props = member.getProperties(); 967 if ((member instanceof RolapMeasure) 968 || (props == null) 969 || (props.length > 3)) 970 { 971 return new HashMap<String, Object>(); 972 } else { 973 return new Flat3Map(); 974 } 975 } 976 } 977 978 /** 979 * <p>Creates the PropertyValueMapFactory which is in turn used 980 * to create property-value maps for member properties.</p> 981 * 982 * <p>The name of the PropertyValueMapFactory is drawn from 983 * {@link mondrian.olap.MondrianProperties#PropertyValueMapFactoryClass} 984 * in mondrian.properties. If unset, it defaults to 985 * {@link RolapMemberBase.DefaultPropertyValueMapFactory}. </p> 986 */ 987 public static final class PropertyValueMapFactoryFactory 988 extends ObjectFactory.Singleton<PropertyValueMapFactory> 989 { 990 /** 991 * Single instance of the <code>PropertyValueMapFactory</code>. 992 */ 993 private static final PropertyValueMapFactoryFactory factory; 994 static { 995 factory = new PropertyValueMapFactoryFactory(); 996 } 997 998 /** 999 * Access the <code>PropertyValueMapFactory</code> instance. 1000 * 1001 * @return the <code>Map</code>. 1002 */ 1003 public static PropertyValueMapFactory getPropertyValueMapFactory() { 1004 return factory.getObject(); 1005 } 1006 1007 /** 1008 * The constructor for the <code>PropertyValueMapFactoryFactory</code>. 1009 * This passes the <code>PropertyValueMapFactory</code> class to the 1010 * <code>ObjectFactory</code> base class. 1011 */ 1012 @SuppressWarnings({"unchecked"}) 1013 private PropertyValueMapFactoryFactory() { 1014 super((Class) PropertyValueMapFactory.class); 1015 } 1016 1017 protected StringProperty getStringProperty() { 1018 return MondrianProperties.instance().PropertyValueMapFactoryClass; 1019 } 1020 1021 protected PropertyValueMapFactory getDefault( 1022 Class[] parameterTypes, 1023 Object[] parameterValues) 1024 throws CreationException 1025 { 1026 return new DefaultPropertyValueMapFactory(); 1027 } 1028 } 1029 1030 public boolean containsAggregateFunction() { 1031 // searching for agg functions is expensive, so cache result 1032 if (containsAggregateFunction == null) { 1033 containsAggregateFunction = 1034 foundAggregateFunction(getExpression()); 1035 } 1036 return containsAggregateFunction; 1037 } 1038 1039 /** 1040 * Returns whether an expression contains a call to an aggregate 1041 * function such as "Aggregate" or "Sum". 1042 * 1043 * @param exp Expression 1044 * @return Whether expression contains a call to an aggregate function. 1045 */ 1046 private static boolean foundAggregateFunction(Exp exp) { 1047 if (exp instanceof ResolvedFunCall) { 1048 ResolvedFunCall resolvedFunCall = (ResolvedFunCall) exp; 1049 if (resolvedFunCall.getFunDef() instanceof AggregateFunDef) { 1050 return true; 1051 } else { 1052 for (Exp argExp : resolvedFunCall.getArgs()) { 1053 if (foundAggregateFunction(argExp)) { 1054 return true; 1055 } 1056 } 1057 } 1058 } 1059 return false; 1060 } 1061 1062 public Calc getCompiledExpression(RolapEvaluatorRoot root) { 1063 return root.getCompiled(getExpression(), true, null); 1064 } 1065 1066 public int getHierarchyOrdinal() { 1067 return getHierarchy().getOrdinalInCube(); 1068 } 1069 1070 public void setContextIn(RolapEvaluator evaluator) { 1071 final RolapMember defaultMember = 1072 evaluator.root.defaultMembers[getHierarchyOrdinal()]; 1073 1074 // This method does not need to call RolapEvaluator.removeCalcMember. 1075 // That happens implicitly in setContext. 1076 evaluator.setContext(defaultMember); 1077 evaluator.setExpanding(this); 1078 } 1079} 1080 1081// End RolapMemberBase.java