001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapHierarchy.java#84 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 2001-2002 Kana Software, Inc. 007 // Copyright (C) 2001-2008 Julian Hyde and others 008 // All Rights Reserved. 009 // You must accept the terms of that agreement to use this software. 010 // 011 // jhyde, 10 August, 2001 012 */ 013 014 package mondrian.rolap; 015 016 import mondrian.olap.*; 017 import mondrian.olap.DimensionType; 018 import mondrian.olap.LevelType; 019 import mondrian.olap.fun.*; 020 import mondrian.olap.type.*; 021 import mondrian.rolap.sql.SqlQuery; 022 import mondrian.resource.MondrianResource; 023 import mondrian.mdx.*; 024 import mondrian.calc.*; 025 import mondrian.calc.impl.*; 026 027 import org.apache.log4j.Logger; 028 029 import java.util.List; 030 import java.io.PrintWriter; 031 032 /** 033 * <code>RolapHierarchy</code> implements {@link Hierarchy} for a ROLAP database. 034 * 035 * @author jhyde 036 * @since 10 August, 2001 037 * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapHierarchy.java#84 $ 038 */ 039 public class RolapHierarchy extends HierarchyBase { 040 041 private static final Logger LOGGER = Logger.getLogger(RolapHierarchy.class); 042 043 /** 044 * The raw member reader. For a member reader which incorporates access 045 * control and deals with hidden members (if the hierarchy is ragged), use 046 * {@link #createMemberReader(Role)}. 047 */ 048 private MemberReader memberReader; 049 protected MondrianDef.Hierarchy xmlHierarchy; 050 private String memberReaderClass; 051 protected MondrianDef.RelationOrJoin relation; 052 private Member defaultMember; 053 private String defaultMemberName; 054 private RolapNullMember nullMember; 055 056 private String sharedHierarchyName; 057 058 private Exp aggregateChildrenExpression; 059 060 /** 061 * Type for members of this hierarchy. Set once to avoid excessive newing. 062 */ 063 final Type memberType = MemberType.forHierarchy(this); 064 065 /** 066 * The level that the null member belongs too. 067 */ 068 protected final RolapLevel nullLevel; 069 070 /** 071 * The 'all' member of this hierarchy. This exists even if the hierarchy 072 * does not officially have an 'all' member. 073 */ 074 private RolapMember allMember; 075 private static final String ALL_LEVEL_CARDINALITY = "1"; 076 077 RolapHierarchy(RolapDimension dimension, String subName, boolean hasAll) { 078 super(dimension, subName, hasAll); 079 this.allLevelName = "(All)"; 080 this.allMemberName = "All " + name + "s"; 081 if (hasAll) { 082 this.levels = new RolapLevel[1]; 083 this.levels[0] = new RolapLevel( 084 this, 0, this.allLevelName, null, null, null, null, 085 null, null, null, RolapProperty.emptyArray, RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE, null, 086 RolapLevel.HideMemberCondition.Never, LevelType.Regular, ""); 087 } else { 088 this.levels = new RolapLevel[0]; 089 } 090 091 // The null member belongs to a level with very similar properties to 092 // the 'all' level. 093 this.nullLevel = new RolapLevel( 094 this, 0, this.allLevelName, null, null, null, null, null, null, 095 null, RolapProperty.emptyArray, 096 RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE, 097 null, 098 RolapLevel.HideMemberCondition.Never, 099 LevelType.Null, ""); 100 } 101 102 /** 103 * Creates a <code>RolapHierarchy</code>. 104 * 105 * @param dimension the dimension this hierarchy belongs to 106 * @param xmlHierarchy the xml object defining this hierarchy 107 * @param xmlCubeDimension the xml object defining the cube 108 * dimension for this object 109 */ 110 RolapHierarchy( 111 RolapDimension dimension, 112 MondrianDef.Hierarchy xmlHierarchy, 113 MondrianDef.CubeDimension xmlCubeDimension) 114 { 115 this(dimension, xmlHierarchy.name, xmlHierarchy.hasAll); 116 117 assert !(this instanceof RolapCubeHierarchy); 118 119 this.xmlHierarchy = xmlHierarchy; 120 this.relation = xmlHierarchy.relation; 121 if (xmlHierarchy.relation instanceof MondrianDef.InlineTable) { 122 this.relation = 123 RolapUtil.convertInlineTableToRelation( 124 (MondrianDef.InlineTable) xmlHierarchy.relation, 125 getRolapSchema().getDialect()); 126 } 127 this.memberReaderClass = xmlHierarchy.memberReaderClass; 128 129 // Create an 'all' level even if the hierarchy does not officially 130 // have one. 131 if (xmlHierarchy.allMemberName != null) { 132 this.allMemberName = xmlHierarchy.allMemberName; 133 } 134 if (xmlHierarchy.allLevelName != null) { 135 this.allLevelName = xmlHierarchy.allLevelName; 136 } 137 RolapLevel allLevel = new RolapLevel( 138 this, 0, this.allLevelName, null, null, null, null, null, null, 139 null, RolapProperty.emptyArray, 140 RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE, 141 null, 142 RolapLevel.HideMemberCondition.Never, 143 LevelType.Regular, ALL_LEVEL_CARDINALITY); 144 allLevel.init(xmlCubeDimension); 145 this.allMember = new RolapMember( 146 null, allLevel, null, allMemberName, Member.MemberType.ALL); 147 // assign "all member" caption 148 if (xmlHierarchy.allMemberCaption != null && 149 xmlHierarchy.allMemberCaption.length() > 0) { 150 this.allMember.setCaption(xmlHierarchy.allMemberCaption); 151 } 152 this.allMember.setOrdinal(0); 153 154 // If the hierarchy has an 'all' member, the 'all' level is level 0. 155 if (hasAll) { 156 this.levels = new RolapLevel[xmlHierarchy.levels.length + 1]; 157 this.levels[0] = allLevel; 158 for (int i = 0; i < xmlHierarchy.levels.length; i++) { 159 final MondrianDef.Level xmlLevel = xmlHierarchy.levels[i]; 160 if (xmlLevel.getKeyExp() == null && 161 xmlHierarchy.memberReaderClass == null) { 162 throw MondrianResource.instance().LevelMustHaveNameExpression.ex(xmlLevel.name); 163 } 164 levels[i + 1] = new RolapLevel(this, i + 1, xmlLevel); 165 } 166 } else { 167 this.levels = new RolapLevel[xmlHierarchy.levels.length]; 168 for (int i = 0; i < xmlHierarchy.levels.length; i++) { 169 levels[i] = new RolapLevel(this, i, xmlHierarchy.levels[i]); 170 } 171 } 172 173 if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) { 174 String sharedDimensionName = 175 ((MondrianDef.DimensionUsage) xmlCubeDimension).source; 176 this.sharedHierarchyName = sharedDimensionName; 177 if (subName != null) { 178 this.sharedHierarchyName += "." + subName; // e.g. "Time.Weekly" 179 } 180 } else { 181 this.sharedHierarchyName = null; 182 } 183 if (xmlHierarchy.relation != null && 184 xmlHierarchy.memberReaderClass != null) { 185 throw MondrianResource.instance(). 186 HierarchyMustNotHaveMoreThanOneSource.ex(getUniqueName()); 187 } 188 if (!Util.isEmpty(xmlHierarchy.caption)) { 189 setCaption(xmlHierarchy.caption); 190 } 191 defaultMemberName = xmlHierarchy.defaultMember; 192 } 193 194 protected Logger getLogger() { 195 return LOGGER; 196 } 197 198 public boolean equals(Object o) { 199 if (this == o) { 200 return true; 201 } 202 if (!(o instanceof RolapHierarchy)) { 203 return false; 204 } 205 206 RolapHierarchy that = (RolapHierarchy)o; 207 if (sharedHierarchyName == null || that.sharedHierarchyName == null) { 208 return false; 209 } else { 210 return sharedHierarchyName.equals(that.sharedHierarchyName) && 211 getUniqueName().equals(that.getUniqueName()); 212 } 213 } 214 215 protected int computeHashCode() { 216 return super.computeHashCode() 217 ^ (sharedHierarchyName == null 218 ? 0 219 : sharedHierarchyName.hashCode()); 220 } 221 222 /** 223 * Initializes a hierarchy within the context of a cube. 224 */ 225 void init(MondrianDef.CubeDimension xmlDimension) { 226 // first create memberReader 227 if (this.memberReader == null) { 228 this.memberReader = getRolapSchema().createMemberReader( 229 sharedHierarchyName, this, memberReaderClass); 230 } 231 for (Level level : levels) { 232 ((RolapLevel) level).init(xmlDimension); 233 } 234 if (defaultMemberName != null) { 235 List<Id.Segment> uniqueNameParts = 236 Util.parseIdentifier(defaultMemberName); 237 238 // We strip off the parent dimension name if the defaultMemberName 239 // is the full unique name, [Time].[2004] rather than simply 240 // [2004]. 241 //Dimension dim = getDimension(); 242 // What we should strip off is hierarchy name 243 if (this.name.equals(uniqueNameParts.get(0).name)) { 244 uniqueNameParts = 245 uniqueNameParts.subList(1, uniqueNameParts.size()); 246 } 247 248 // Now lookup the name from the hierarchy's members. 249 defaultMember = memberReader.lookupMember(uniqueNameParts, false); 250 if (defaultMember == null) { 251 throw Util.newInternal( 252 "Can not find Default Member with name \"" 253 + defaultMemberName + "\" in Hierarchy \"" + 254 getName() + "\""); 255 } 256 } 257 } 258 259 void setMemberReader(MemberReader memberReader) { 260 this.memberReader = memberReader; 261 } 262 263 MemberReader getMemberReader() { 264 return this.memberReader; 265 } 266 267 RolapLevel newMeasuresLevel() { 268 RolapLevel level = 269 new RolapLevel( 270 this, this.levels.length, 271 "MeasuresLevel", null, null, null, null, 272 null, null, null, RolapProperty.emptyArray, 0, null, 273 RolapLevel.HideMemberCondition.Never, LevelType.Regular, ""); 274 this.levels = RolapUtil.addElement(this.levels, level); 275 return level; 276 } 277 278 /** 279 * If this hierarchy has precisely one table, returns that table; 280 * if this hierarchy has no table, return the cube's fact-table; 281 * otherwise, returns null. 282 */ 283 MondrianDef.Relation getUniqueTable() { 284 if (relation instanceof MondrianDef.Relation) { 285 return (MondrianDef.Relation) relation; 286 } else if (relation instanceof MondrianDef.Join) { 287 return null; 288 } else { 289 throw Util.newInternal( 290 "hierarchy's relation is a " + relation.getClass()); 291 } 292 } 293 294 boolean tableExists(String tableName) { 295 return (relation != null) && tableExists(tableName, relation); 296 } 297 298 private static boolean tableExists( 299 String tableName, 300 MondrianDef.RelationOrJoin relationOrJoin) 301 { 302 if (relationOrJoin instanceof MondrianDef.Relation) { 303 MondrianDef.Relation relation = 304 (MondrianDef.Relation) relationOrJoin; 305 return relation.getAlias().equals(tableName); 306 } else { 307 MondrianDef.Join join = (MondrianDef.Join) relationOrJoin; 308 return tableExists(tableName, join.left) || 309 tableExists(tableName, join.right); 310 } 311 } 312 313 public RolapSchema getRolapSchema() { 314 return (RolapSchema) dimension.getSchema(); 315 } 316 317 public MondrianDef.RelationOrJoin getRelation() { 318 return relation; 319 } 320 321 public MondrianDef.Hierarchy getXmlHierarchy() { 322 return xmlHierarchy; 323 } 324 325 public Member getDefaultMember() { 326 // use lazy initialization to get around bootstrap issues 327 if (defaultMember == null) { 328 List rootMembers = memberReader.getRootMembers(); 329 if (rootMembers.size() == 0) { 330 throw MondrianResource.instance().InvalidHierarchyCondition.ex(this.getUniqueName()); 331 } 332 defaultMember = (RolapMember) rootMembers.get(0); 333 } 334 return defaultMember; 335 } 336 337 public Member getNullMember() { 338 // use lazy initialization to get around bootstrap issues 339 if (nullMember == null) { 340 nullMember = new RolapNullMember(nullLevel); 341 } 342 return nullMember; 343 } 344 345 /** 346 * Returns the 'all' member. 347 */ 348 public RolapMember getAllMember() { 349 return allMember; 350 } 351 352 public Member createMember( 353 Member parent, 354 Level level, 355 String name, 356 Formula formula) { 357 if (formula == null) { 358 return new RolapMember( 359 (RolapMember) parent, (RolapLevel) level, name); 360 } else if (level.getDimension().isMeasures()) { 361 return new RolapCalculatedMeasure( 362 (RolapMember) parent, (RolapLevel) level, name, formula); 363 } else { 364 return new RolapCalculatedMember( 365 (RolapMember) parent, (RolapLevel) level, name, formula); 366 } 367 } 368 369 String getAlias() { 370 return getName(); 371 } 372 373 /** 374 * Returns the name of the source hierarchy, if this hierarchy is shared, 375 * otherwise null. 376 * 377 * <p>If this hierarchy is a public -- that is, it belongs to a dimension 378 * which is a usage of a shared dimension -- then 379 * <code>sharedHierarchyName</code> holds the unique name of the shared 380 * hierarchy; otherwise it is null. 381 * 382 * <p> Suppose this hierarchy is "Weekly" in the dimension "Order Date" of 383 * cube "Sales", and that "Order Date" is a usage of the "Time" 384 * dimension. Then <code>sharedHierarchyName</code> will be 385 * "[Time].[Weekly]". 386 */ 387 public String getSharedHierarchyName() { 388 return sharedHierarchyName; 389 } 390 391 /** 392 * Adds to the FROM clause of the query the tables necessary to access the 393 * members of this hierarchy. If <code>expression</code> is not null, adds 394 * the tables necessary to compute that expression. 395 * 396 * <p> This method is idempotent: if you call it more than once, it only 397 * adds the table(s) to the FROM clause once. 398 * 399 * @param query Query to add the hierarchy to 400 * @param expression Level to qualify up to; if null, qualifies up to the 401 * topmost ('all') expression, which may require more columns and more joins 402 */ 403 void addToFrom(SqlQuery query, MondrianDef.Expression expression) { 404 if (relation == null) { 405 throw Util.newError( 406 "cannot add hierarchy " + getUniqueName() + 407 " to query: it does not have a <Table>, <View> or <Join>"); 408 } 409 final boolean failIfExists = false; 410 MondrianDef.RelationOrJoin subRelation = relation; 411 if (relation instanceof MondrianDef.Join) { 412 if (expression != null) { 413 // Suppose relation is 414 // (((A join B) join C) join D) 415 // and the fact table is 416 // F 417 // and our expression uses C. We want to make the expression 418 // F left join ((A join B) join C). 419 // Search for the smallest subset of the relation which 420 // uses C. 421 subRelation = relationSubset(relation, expression.getTableAlias()); 422 423 } 424 } 425 query.addFrom(subRelation, null, failIfExists); 426 } 427 428 /** 429 * Adds a table to the FROM clause of the query. 430 * If <code>table</code> is not null, adds the table. Otherwise, add the 431 * relation on which this hierarchy is based on. 432 * 433 * <p> This method is idempotent: if you call it more than once, it only 434 * adds the table(s) to the FROM clause once. 435 * 436 * @param query Query to add the hierarchy to 437 * @param table table to add to the query 438 */ 439 void addToFrom(SqlQuery query, RolapStar.Table table) { 440 if (getRelation() == null) { 441 throw Util.newError( 442 "cannot add hierarchy " + getUniqueName() + 443 " to query: it does not have a <Table>, <View> or <Join>"); 444 } 445 final boolean failIfExists = false; 446 MondrianDef.RelationOrJoin subRelation = null; 447 if (table != null) { 448 // Suppose relation is 449 // (((A join B) join C) join D) 450 // and the fact table is 451 // F 452 // and the table to add is C. We want to make the expression 453 // F left join ((A join B) join C). 454 // Search for the smallest subset of the relation which 455 // joins with C. 456 subRelation = lookupRelationSubset(getRelation(), table); 457 } 458 459 if (subRelation == null) { 460 // If no table is found or specified, add the entire base relation. 461 subRelation = getRelation(); 462 } 463 464 query.addFrom(subRelation, null, failIfExists); 465 } 466 467 /** 468 * Returns the smallest subset of <code>relation</code> which contains 469 * the relation <code>alias</code>, or null if these is no relation with 470 * such an alias. 471 * @param relation the relation in which to look for table by its alias 472 * @param alias table alias to search for 473 * @return the smallest containing relation or null if no matching table 474 * is found in <code>relation</code> 475 */ 476 private static MondrianDef.RelationOrJoin relationSubset( 477 MondrianDef.RelationOrJoin relation, 478 String alias) 479 { 480 if (relation instanceof MondrianDef.Relation) { 481 MondrianDef.Relation table = 482 (MondrianDef.Relation) relation; 483 return table.getAlias().equals(alias) 484 ? relation 485 : null; 486 487 } else if (relation instanceof MondrianDef.Join) { 488 MondrianDef.Join join = (MondrianDef.Join) relation; 489 MondrianDef.RelationOrJoin rightRelation = relationSubset(join.right, alias); 490 return (rightRelation == null) 491 ? relationSubset(join.left, alias) 492 : join; 493 494 } else { 495 throw Util.newInternal("bad relation type " + relation); 496 } 497 } 498 499 /** 500 * Returns the smallest subset of <code>relation</code> which contains 501 * the table <code>targetTable</code>, or null if the targetTable is not 502 * one of the joining table in <code>relation</code>. 503 * 504 * @param relation the relation in which to look for targetTable 505 * @param targetTable table to add to the query 506 * @return the smallest containing relation or null if no matching table 507 * is found in <code>relation</code> 508 */ 509 private static MondrianDef.RelationOrJoin lookupRelationSubset( 510 MondrianDef.RelationOrJoin relation, 511 RolapStar.Table targetTable) 512 { 513 if (relation instanceof MondrianDef.Table) { 514 MondrianDef.Table table = (MondrianDef.Table) relation; 515 if (table.name.equals(targetTable.getTableName())) { 516 return relation; 517 } else { 518 // Not the same table if table names are different 519 return null; 520 } 521 } else if (relation instanceof MondrianDef.Join) { 522 // Search inside relation, starting from the rightmost table, 523 // and move left along the join chain. 524 MondrianDef.Join join = (MondrianDef.Join) relation; 525 MondrianDef.RelationOrJoin rightRelation = 526 lookupRelationSubset(join.right, targetTable); 527 if (rightRelation == null) { 528 // Keep searching left. 529 return lookupRelationSubset( 530 join.left, targetTable); 531 } else { 532 // Found a match. 533 return join; 534 } 535 } 536 return null; 537 } 538 539 /** 540 * Creates a member reader which enforces the access-control profile of 541 * <code>role</code>. 542 * 543 * <p>This method may not be efficient, so the caller should take care 544 * not to call it too often. A cache is a good idea. 545 * 546 * @pre role != null 547 * @post return != null 548 */ 549 MemberReader createMemberReader(Role role) { 550 final Access access = role.getAccess(this); 551 switch (access) { 552 case NONE: 553 throw Util.newInternal("Illegal access to members of hierarchy " 554 + this); 555 case ALL: 556 return (isRagged()) 557 ? new RestrictedMemberReader(getMemberReader(), role) 558 : getMemberReader(); 559 560 case CUSTOM: 561 final Role.HierarchyAccess hierarchyAccess = 562 role.getAccessDetails(this); 563 final Role.RollupPolicy rollupPolicy = 564 hierarchyAccess.getRollupPolicy(); 565 final NumericType returnType = new NumericType(); 566 switch (rollupPolicy) { 567 case FULL: 568 return new RestrictedMemberReader(getMemberReader(), role); 569 case PARTIAL: 570 Type memberType1 = 571 new mondrian.olap.type.MemberType( 572 getDimension(), 573 getHierarchy(), 574 null, 575 null); 576 SetType setType = new SetType(memberType1); 577 ListCalc listCalc = 578 new AbstractMemberListCalc( 579 new DummyExp(setType), new Calc[0]) 580 { 581 public List<Member> evaluateMemberList( 582 Evaluator evaluator) { 583 return FunUtil.getNonEmptyMemberChildren( 584 evaluator, 585 ((RolapEvaluator) evaluator).getExpanding()); 586 } 587 588 public boolean dependsOn(Dimension dimension) { 589 return true; 590 } 591 }; 592 final Calc partialCalc = 593 new LimitedRollupAggregateCalc(returnType, listCalc); 594 595 final Exp partialExp = 596 new ResolvedFunCall( 597 new FunDefBase("$x", "x", "In") { 598 public Calc compileCall( 599 ResolvedFunCall call, 600 ExpCompiler compiler) 601 { 602 return partialCalc; 603 } 604 605 public void unparse(Exp[] args, PrintWriter pw) { 606 pw.print("$RollupAccessibleChildren()"); 607 } 608 }, 609 new Exp[0], 610 returnType); 611 return new LimitedRollupSubstitutingMemberReader( 612 role, hierarchyAccess, partialExp); 613 614 case HIDDEN: 615 Exp hiddenExp = 616 new ResolvedFunCall( 617 new FunDefBase("$x", "x", "In") { 618 public Calc compileCall( 619 ResolvedFunCall call, ExpCompiler compiler) 620 { 621 return new ConstantCalc(returnType, null); 622 } 623 624 public void unparse(Exp[] args, PrintWriter pw) { 625 pw.print("$RollupAccessibleChildren()"); 626 } 627 }, 628 new Exp[0], 629 returnType); 630 return new LimitedRollupSubstitutingMemberReader( 631 role, hierarchyAccess, hiddenExp); 632 default: 633 throw Util.unexpected(rollupPolicy); 634 } 635 default: 636 throw Util.badValue(access); 637 } 638 } 639 640 /** 641 * A hierarchy is ragged if it contains one or more levels with hidden 642 * members. 643 */ 644 public boolean isRagged() { 645 for (Level level : levels) { 646 if (((RolapLevel) level).getHideMemberCondition() != 647 RolapLevel.HideMemberCondition.Never) { 648 return true; 649 } 650 } 651 return false; 652 } 653 654 /** 655 * Returns an expression which will compute a member's value by aggregating 656 * its children. 657 * 658 * <p>It is efficient to share one expression between all calculated members in 659 * a parent-child hierarchy, so we only need need to validate the expression 660 * once. 661 */ 662 synchronized Exp getAggregateChildrenExpression() { 663 if (aggregateChildrenExpression == null) { 664 UnresolvedFunCall fc = new UnresolvedFunCall( 665 "$AggregateChildren", 666 Syntax.Internal, 667 new Exp[] {new HierarchyExpr(this)}); 668 Validator validator = 669 Util.createSimpleValidator(BuiltinFunTable.instance()); 670 aggregateChildrenExpression = fc.accept(validator); 671 } 672 return aggregateChildrenExpression; 673 } 674 675 /** 676 * Builds a dimension which maps onto a table holding the transitive 677 * closure of the relationship for this parent-child level. 678 * 679 * <p>This method is triggered by the 680 * {@link mondrian.olap.MondrianDef.Closure} element 681 * in a schema, and is only meaningful for a parent-child hierarchy. 682 * 683 * <p>When a Schema contains a parent-child Hierarchy that has an 684 * associated closure table, Mondrian creates a parallel internal 685 * Hierarchy, called a "closed peer", that refers to the closure table. 686 * This is indicated in the schema at the level of a Level, by including a 687 * Closure element. The closure table represents 688 * the transitive closure of the parent-child relationship. 689 * 690 * <p>The peer dimension, with its single hierarchy, and 3 levels (all, 691 * closure, item) really 'belong to' the parent-child level. If a single 692 * hierarchy had two parent-child levels (however unlikely this might be) 693 * then each level would have its own auxiliary dimension. 694 * 695 * <p>For example, in the demo schema the [HR].[Employee] dimension 696 * contains a parent-child hierarchy: 697 * 698 * <pre> 699 * <Dimension name="Employees" foreignKey="employee_id"> 700 * <Hierarchy hasAll="true" allMemberName="All Employees" 701 * primaryKey="employee_id"> 702 * <Table name="employee"/> 703 * <Level name="Employee Id" type="Numeric" uniqueMembers="true" 704 * column="employee_id" parentColumn="supervisor_id" 705 * nameColumn="full_name" nullParentValue="0"> 706 * <Closure parentColumn="supervisor_id" childColumn="employee_id"> 707 * <Table name="employee_closure"/> 708 * </Closure> 709 * ... 710 * </pre> 711 * The internal closed peer Hierarchy has this structure: 712 * <pre> 713 * <Dimension name="Employees" foreignKey="employee_id"> 714 * ... 715 * <Hierarchy name="Employees$Closure" 716 * hasAll="true" allMemberName="All Employees" 717 * primaryKey="employee_id" primaryKeyTable="employee_closure"> 718 * <Join leftKey="supervisor_id" rightKey="employee_id"> 719 * <Table name="employee_closure"/> 720 * <Table name="employee"/> 721 * </Join> 722 * <Level name="Closure" type="Numeric" uniqueMembers="false" 723 * table="employee_closure" column="supervisor_id"/> 724 * <Level name="Employee" type="Numeric" uniqueMembers="true" 725 * table="employee_closure" column="employee_id"/> 726 * </Hierarchy> 727 * </pre> 728 * 729 * <p>Note that the original Level with the Closure produces two Levels in 730 * the closed peer Hierarchy: a simple peer (Employee) and a closed peer 731 * (Closure). 732 * 733 * @param src a parent-child Level that has a Closure clause 734 * @param clos a Closure clause 735 * @return the closed peer Level in the closed peer Hierarchy 736 */ 737 RolapDimension createClosedPeerDimension( 738 RolapLevel src, 739 MondrianDef.Closure clos, 740 MondrianDef.CubeDimension xmlDimension) { 741 742 // REVIEW (mb): What about attribute primaryKeyTable? 743 744 // Create a peer dimension. 745 RolapDimension peerDimension = new RolapDimension( 746 dimension.getSchema(), 747 dimension.getName() + "$Closure", 748 DimensionType.StandardDimension, 749 dimension.isHighCardinality()); 750 751 // Create a peer hierarchy. 752 RolapHierarchy peerHier = peerDimension.newHierarchy(subName, true); 753 peerHier.allMemberName = getAllMemberName(); 754 peerHier.allMember = getAllMember(); 755 peerHier.allLevelName = getAllLevelName(); 756 peerHier.sharedHierarchyName = getSharedHierarchyName(); 757 MondrianDef.Join join = new MondrianDef.Join(); 758 peerHier.relation = join; 759 join.left = clos.table; // the closure table 760 join.leftKey = clos.parentColumn; 761 join.right = relation; // the unclosed base table 762 join.rightKey = clos.childColumn; 763 764 // Create the upper level. 765 // This represents all groups of descendants. For example, in the 766 // Employee closure hierarchy, this level has a row for every employee. 767 int index = peerHier.levels.length; 768 int flags = src.getFlags() &~ RolapLevel.FLAG_UNIQUE; 769 MondrianDef.Expression keyExp = 770 new MondrianDef.Column(clos.table.name, clos.parentColumn); 771 772 RolapLevel level = new RolapLevel(peerHier, index++, 773 "Closure", 774 keyExp, null, null, null, 775 null, null, // no longer a parent-child hierarchy 776 null, 777 RolapProperty.emptyArray, 778 flags, 779 src.getDatatype(), 780 src.getHideMemberCondition(), 781 src.getLevelType(), 782 ""); 783 peerHier.levels = RolapUtil.addElement(peerHier.levels, level); 784 785 // Create lower level. 786 // This represents individual items. For example, in the Employee 787 // closure hierarchy, this level has a row for every direct and 788 // indirect report of every employee (which is more than the number 789 // of employees). 790 flags = src.getFlags() | RolapLevel.FLAG_UNIQUE; 791 keyExp = new MondrianDef.Column(clos.table.name, clos.childColumn); 792 RolapLevel sublevel = new RolapLevel( 793 peerHier, 794 index++, 795 "Item", 796 keyExp, 797 null, 798 null, 799 null, 800 null, 801 null, // no longer a parent-child hierarchy 802 null, 803 RolapProperty.emptyArray, 804 flags, 805 src.getDatatype(), 806 src.getHideMemberCondition(), 807 src.getLevelType(), 808 ""); 809 peerHier.levels = RolapUtil.addElement(peerHier.levels, sublevel); 810 811 return peerDimension; 812 } 813 814 /** 815 * Sets default member of this Hierarchy. 816 * 817 * @param defaultMember Default member 818 */ 819 public void setDefaultMember(Member defaultMember) { 820 if (defaultMember != null) { 821 this.defaultMember = defaultMember; 822 } 823 } 824 825 826 /** 827 * A <code>RolapNullMember</code> is the null member of its hierarchy. 828 * Every hierarchy has precisely one. They are yielded by operations such as 829 * <code>[Gender].[All].ParentMember</code>. Null members are usually 830 * omitted from sets (in particular, in the set constructor operator "{ ... 831 * }". 832 */ 833 static class RolapNullMember extends RolapMember { 834 RolapNullMember(final RolapLevel level) { 835 super(null, level, null, RolapUtil.mdxNullLiteral, MemberType.NULL); 836 assert level != null; 837 } 838 } 839 840 /** 841 * Calculated member which is also a measure (that is, a member of the 842 * [Measures] dimension). 843 */ 844 protected static class RolapCalculatedMeasure 845 extends RolapCalculatedMember 846 implements RolapMeasure 847 { 848 private CellFormatter cellFormatter; 849 850 public RolapCalculatedMeasure( 851 RolapMember parent, RolapLevel level, String name, Formula formula) 852 { 853 super(parent, level, name, formula); 854 } 855 856 public synchronized void setProperty(String name, Object value) { 857 if (name.equals(Property.CELL_FORMATTER.getName())) { 858 String cellFormatterClass = (String) value; 859 try { 860 this.cellFormatter = 861 RolapCube.getCellFormatter(cellFormatterClass); 862 } catch (Exception e) { 863 throw MondrianResource.instance().CellFormatterLoadFailed.ex( 864 cellFormatterClass, getUniqueName(), e); 865 } 866 } 867 super.setProperty(name, value); 868 } 869 870 public CellFormatter getFormatter() { 871 return cellFormatter; 872 } 873 } 874 875 /** 876 * Substitute for a member in a hierarchy whose rollup policy is 'partial' 877 * or 'hidden'. The member is calculated using an expression which 878 * aggregates only visible descendants. 879 * 880 * <p>Note that this class extends RolapCubeMember only because other code 881 * expects that all members in a RolapCubeHierarchy are RolapCubeMembers. 882 * 883 * @see mondrian.olap.Role.RollupPolicy 884 */ 885 public static class LimitedRollupMember extends RolapCubeMember { 886 public final RolapMember member; 887 private final Exp exp; 888 889 LimitedRollupMember( 890 RolapCubeMember member, 891 Exp exp) 892 { 893 super( 894 member.getParentMember(), 895 member.getRolapMember(), 896 member.getLevel(), 897 member.getCube()); 898 assert !(member instanceof LimitedRollupMember); 899 this.member = member; 900 this.exp = exp; 901 } 902 903 public boolean equals(Object o) { 904 return o instanceof LimitedRollupMember 905 && ((LimitedRollupMember) o).member.equals(member); 906 } 907 908 public int hashCode() { 909 return member.hashCode(); 910 } 911 912 public Exp getExpression() { 913 return exp; 914 } 915 916 protected boolean computeCalculated(final MemberType memberType) { 917 return true; 918 } 919 920 public boolean isCalculated() { 921 return true; 922 } 923 } 924 925 /** 926 * Member reader which wraps a hierarchy's member reader, and if the 927 * role has limited access to the hierarchy, replaces members with 928 * dummy members which evaluate to the sum of only the accessible children. 929 */ 930 private class LimitedRollupSubstitutingMemberReader 931 extends SubstitutingMemberReader 932 { 933 private final Role.HierarchyAccess hierarchyAccess; 934 private final Exp exp; 935 936 public LimitedRollupSubstitutingMemberReader( 937 Role role, 938 Role.HierarchyAccess hierarchyAccess, 939 Exp exp) 940 { 941 super( 942 new RestrictedMemberReader( 943 RolapHierarchy.this.getMemberReader(), role)); 944 this.hierarchyAccess = hierarchyAccess; 945 this.exp = exp; 946 } 947 948 @Override 949 public RolapMember substitute(final RolapMember member) { 950 if (member != null 951 && (hierarchyAccess.getAccess(member) == Access.CUSTOM 952 || hierarchyAccess.hasInaccessibleDescendants(member))) 953 { 954 // Member is visible, but at least one of its 955 // descendants is not. 956 return new LimitedRollupMember((RolapCubeMember)member, exp); 957 } else { 958 // No need to substitute. Member and all of its 959 // descendants are accessible. Total for member 960 // is same as for FULL policy. 961 return member; 962 } 963 } 964 965 @Override 966 public RolapMember desubstitute(RolapMember member) { 967 if (member instanceof LimitedRollupMember) { 968 return ((LimitedRollupMember) member).member; 969 } else { 970 return member; 971 } 972 } 973 } 974 975 /** 976 * Compiled expression that computes rollup over a set of visible children. 977 * The {@code listCalc} expression determines that list of children. 978 */ 979 private static class LimitedRollupAggregateCalc 980 extends AggregateFunDef.AggregateCalc 981 { 982 public LimitedRollupAggregateCalc(Type returnType, ListCalc listCalc) { 983 super( 984 new DummyExp(returnType), 985 listCalc, 986 new ValueCalc(new DummyExp(returnType))); 987 } 988 } 989 } 990 991 // End RolapHierarchy.java