001    /*
002    // $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapHierarchy.java#6 $
003    // This software is subject to the terms of the Eclipse Public License v1.0
004    // Agreement, available at the following URL:
005    // http://www.eclipse.org/legal/epl-v10.html.
006    // Copyright (C) 2001-2002 Kana Software, Inc.
007    // Copyright (C) 2001-2010 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    import mondrian.util.UnionIterator;
027    
028    import org.apache.log4j.Logger;
029    
030    import java.util.*;
031    import java.io.PrintWriter;
032    
033    /**
034     * <code>RolapHierarchy</code> implements {@link Hierarchy} for a ROLAP database.
035     *
036     * <p>The ordinal of a hierarchy <em>within a particular cube</em> is found by
037     * calling {@link #getOrdinalInCube()}. Ordinals are contiguous and zero-based.
038     * Zero is always the <code>[Measures]</code> dimension.
039     *
040     * <p>NOTE: It is only valid to call that method on the measures hierarchy, and
041     * on members of the {@link RolapCubeHierarchy} subclass. When the measures
042     * hierarchy is of that class, we will move the method down.)
043     *
044     * @author jhyde
045     * @since 10 August, 2001
046     * @version $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/RolapHierarchy.java#6 $
047     */
048    public class RolapHierarchy extends HierarchyBase {
049    
050        private static final Logger LOGGER = Logger.getLogger(RolapHierarchy.class);
051    
052        /**
053         * The raw member reader. For a member reader which incorporates access
054         * control and deals with hidden members (if the hierarchy is ragged), use
055         * {@link #createMemberReader(Role)}.
056         */
057        private MemberReader memberReader;
058        protected MondrianDef.Hierarchy xmlHierarchy;
059        private String memberReaderClass;
060        protected MondrianDef.RelationOrJoin relation;
061        private Member defaultMember;
062        private String defaultMemberName;
063        private RolapNullMember nullMember;
064    
065        private String sharedHierarchyName;
066        private String uniqueKeyLevelName;
067    
068        private Exp aggregateChildrenExpression;
069    
070        /**
071         * The level that the null member belongs too.
072         */
073        protected final RolapLevel nullLevel;
074    
075        /**
076         * The 'all' member of this hierarchy. This exists even if the hierarchy
077         * does not officially have an 'all' member.
078         */
079        private RolapMemberBase allMember;
080        private static final String ALL_LEVEL_CARDINALITY = "1";
081        private final Map<String, Annotation> annotationMap;
082        final RolapHierarchy closureFor;
083    
084        /**
085         * Creates a hierarchy.
086         *
087         * @param dimension Dimension
088         * @param subName Name of this hierarchy
089         * @param hasAll Whether hierarchy has an 'all' member
090         * @param closureFor Hierarchy for which the new hierarchy is a closure;
091         *     null for regular hierarchies
092         */
093        RolapHierarchy(
094            RolapDimension dimension,
095            String subName,
096            String caption,
097            String description,
098            boolean hasAll,
099            RolapHierarchy closureFor,
100            Map<String, Annotation> annotationMap)
101        {
102            super(dimension, subName, caption, description, hasAll);
103            this.annotationMap = annotationMap;
104            this.allLevelName = "(All)";
105            this.allMemberName =
106                subName != null
107                && (MondrianProperties.instance().SsasCompatibleNaming.get()
108                    || name.equals(subName + "." + subName))
109                    ? "All " + subName + "s"
110                    : "All " + name + "s";
111            this.closureFor = closureFor;
112            if (hasAll) {
113                this.levels = new RolapLevel[1];
114                this.levels[0] =
115                    new RolapLevel(
116                        this,
117                        this.allLevelName,
118                        null,
119                        null,
120                        0,
121                        null,
122                        null,
123                        null,
124                        null,
125                        null,
126                        null,
127                        null,
128                        RolapProperty.emptyArray,
129                        RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE,
130                        null,
131                        RolapLevel.HideMemberCondition.Never,
132                        LevelType.Regular,
133                        "",
134                        Collections.<String, Annotation>emptyMap());
135            } else {
136                this.levels = new RolapLevel[0];
137            }
138    
139            // The null member belongs to a level with very similar properties to
140            // the 'all' level.
141            this.nullLevel =
142                new RolapLevel(
143                    this,
144                    this.allLevelName,
145                    null,
146                    null,
147                    0,
148                    null,
149                    null,
150                    null,
151                    null,
152                    null,
153                    null,
154                    null,
155                    RolapProperty.emptyArray,
156                    RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE,
157                    null,
158                    RolapLevel.HideMemberCondition.Never,
159                    LevelType.Null,
160                    "",
161                    Collections.<String, Annotation>emptyMap());
162        }
163    
164        /**
165         * Creates a <code>RolapHierarchy</code>.
166         *
167         * @param dimension the dimension this hierarchy belongs to
168         * @param xmlHierarchy the xml object defining this hierarchy
169         * @param xmlCubeDimension the xml object defining the cube
170         *   dimension for this object
171         */
172        RolapHierarchy(
173            RolapDimension dimension,
174            MondrianDef.Hierarchy xmlHierarchy,
175            MondrianDef.CubeDimension xmlCubeDimension)
176        {
177            this(
178                dimension,
179                xmlHierarchy.name,
180                xmlHierarchy.caption,
181                xmlHierarchy.description,
182                xmlHierarchy.hasAll,
183                null,
184                createAnnotationMap(xmlHierarchy.annotations));
185    
186            assert !(this instanceof RolapCubeHierarchy);
187    
188            this.xmlHierarchy = xmlHierarchy;
189            this.relation = xmlHierarchy.relation;
190            if (xmlHierarchy.relation instanceof MondrianDef.InlineTable) {
191                this.relation =
192                    RolapUtil.convertInlineTableToRelation(
193                        (MondrianDef.InlineTable) xmlHierarchy.relation,
194                        getRolapSchema().getDialect());
195            }
196            this.memberReaderClass = xmlHierarchy.memberReaderClass;
197            this.uniqueKeyLevelName = xmlHierarchy.uniqueKeyLevelName;
198    
199            // Create an 'all' level even if the hierarchy does not officially
200            // have one.
201            if (xmlHierarchy.allMemberName != null) {
202                this.allMemberName = xmlHierarchy.allMemberName;
203            }
204            if (xmlHierarchy.allLevelName != null) {
205                this.allLevelName = xmlHierarchy.allLevelName;
206            }
207            RolapLevel allLevel =
208                new RolapLevel(
209                    this,
210                    this.allLevelName,
211                    null,
212                    null,
213                    0,
214                    null,
215                    null,
216                    null,
217                    null,
218                    null,
219                    null,
220                    null,
221                    RolapProperty.emptyArray,
222                    RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE,
223                    null,
224                    RolapLevel.HideMemberCondition.Never,
225                    LevelType.Regular, ALL_LEVEL_CARDINALITY,
226                    Collections.<String, Annotation>emptyMap());
227            allLevel.init(xmlCubeDimension);
228            this.allMember = new RolapMemberBase(
229                null, allLevel, null, allMemberName, Member.MemberType.ALL);
230            // assign "all member" caption
231            if (xmlHierarchy.allMemberCaption != null
232                && xmlHierarchy.allMemberCaption.length() > 0)
233            {
234                this.allMember.setCaption(xmlHierarchy.allMemberCaption);
235            }
236            this.allMember.setOrdinal(0);
237    
238            if (xmlHierarchy.levels.length == 0) {
239                throw MondrianResource.instance().HierarchyHasNoLevels.ex(
240                    getUniqueName());
241            }
242    
243            Set<String> levelNameSet = new HashSet<String>();
244            for (MondrianDef.Level level : xmlHierarchy.levels) {
245                if (!levelNameSet.add(level.name)) {
246                    throw MondrianResource.instance().HierarchyLevelNamesNotUnique
247                        .ex(
248                            getUniqueName(), level.name);
249                }
250            }
251    
252            // If the hierarchy has an 'all' member, the 'all' level is level 0.
253            if (hasAll) {
254                this.levels = new RolapLevel[xmlHierarchy.levels.length + 1];
255                this.levels[0] = allLevel;
256                for (int i = 0; i < xmlHierarchy.levels.length; i++) {
257                    final MondrianDef.Level xmlLevel = xmlHierarchy.levels[i];
258                    if (xmlLevel.getKeyExp() == null
259                        && xmlHierarchy.memberReaderClass == null)
260                    {
261                        throw MondrianResource.instance()
262                            .LevelMustHaveNameExpression.ex(xmlLevel.name);
263                    }
264                    levels[i + 1] = new RolapLevel(this, i + 1, xmlLevel);
265                }
266            } else {
267                this.levels = new RolapLevel[xmlHierarchy.levels.length];
268                for (int i = 0; i < xmlHierarchy.levels.length; i++) {
269                    levels[i] = new RolapLevel(this, i, xmlHierarchy.levels[i]);
270                }
271            }
272    
273            if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) {
274                String sharedDimensionName =
275                    ((MondrianDef.DimensionUsage) xmlCubeDimension).source;
276                this.sharedHierarchyName = sharedDimensionName;
277                if (subName != null) {
278                    this.sharedHierarchyName += "." + subName; // e.g. "Time.Weekly"
279                }
280            } else {
281                this.sharedHierarchyName = null;
282            }
283            if (xmlHierarchy.relation != null
284                && xmlHierarchy.memberReaderClass != null)
285            {
286                throw MondrianResource.instance()
287                    .HierarchyMustNotHaveMoreThanOneSource.ex(getUniqueName());
288            }
289            if (!Util.isEmpty(xmlHierarchy.caption)) {
290                setCaption(xmlHierarchy.caption);
291            }
292            defaultMemberName = xmlHierarchy.defaultMember;
293        }
294    
295        public static Map<String, Annotation> createAnnotationMap(
296            MondrianDef.Annotations annotations)
297        {
298            if (annotations == null
299                || annotations.array == null
300                || annotations.array.length == 0)
301            {
302                return Collections.emptyMap();
303            }
304            // Use linked hash map because it retains order.
305            final Map<String, Annotation> map =
306                new LinkedHashMap<String, Annotation>();
307            for (MondrianDef.Annotation annotation : annotations.array) {
308                final String name = annotation.name;
309                final String value = annotation.cdata;
310                map.put(
311                    annotation.name,
312                    new Annotation() {
313                        public String getName() {
314                            return name;
315                        }
316    
317                        public Object getValue() {
318                            return value;
319                        }
320                    });
321            }
322            return map;
323        }
324    
325        protected Logger getLogger() {
326            return LOGGER;
327        }
328    
329        public boolean equals(Object o) {
330            if (this == o) {
331                return true;
332            }
333            if (!(o instanceof RolapHierarchy)) {
334                return false;
335            }
336    
337            RolapHierarchy that = (RolapHierarchy)o;
338            if (sharedHierarchyName == null || that.sharedHierarchyName == null) {
339                return false;
340            } else {
341                return sharedHierarchyName.equals(that.sharedHierarchyName)
342                    && getUniqueName().equals(that.getUniqueName());
343            }
344        }
345    
346        protected int computeHashCode() {
347            return super.computeHashCode()
348                ^ (sharedHierarchyName == null
349                    ? 0
350                    : sharedHierarchyName.hashCode());
351        }
352    
353        /**
354         * Initializes a hierarchy within the context of a cube.
355         */
356        void init(MondrianDef.CubeDimension xmlDimension) {
357            // first create memberReader
358            if (this.memberReader == null) {
359                this.memberReader = getRolapSchema().createMemberReader(
360                    sharedHierarchyName, this, memberReaderClass);
361            }
362            for (Level level : levels) {
363                ((RolapLevel) level).init(xmlDimension);
364            }
365            if (defaultMemberName != null) {
366                List<Id.Segment> uniqueNameParts;
367                if (defaultMemberName.contains("[")) {
368                    uniqueNameParts = Util.parseIdentifier(defaultMemberName);
369                } else {
370                    uniqueNameParts =
371                        Collections.singletonList(
372                            new Id.Segment(defaultMemberName, Id.Quoting.UNQUOTED));
373                }
374    
375                // First look up from within this hierarchy. Works for unqualified
376                // names, e.g. [USA].[CA].
377                defaultMember = (Member) Util.lookupCompound(
378                    getRolapSchema().getSchemaReader(),
379                    this,
380                    uniqueNameParts,
381                    false,
382                    Category.Member,
383                    MatchType.EXACT);
384    
385                // Next look up within global context. Works for qualified names,
386                // e.g. [Store].[USA].[CA] or [Time].[Weekly].[1997].[Q2].
387                if (defaultMember == null) {
388                    defaultMember = (Member) Util.lookupCompound(
389                        getRolapSchema().getSchemaReader(),
390                        new DummyElement(),
391                        uniqueNameParts,
392                        false,
393                        Category.Member,
394                        MatchType.EXACT);
395                }
396                if (defaultMember == null) {
397                    throw Util.newInternal(
398                        "Can not find Default Member with name \""
399                        + defaultMemberName + "\" in Hierarchy \""
400                        + getName() + "\"");
401                }
402            }
403        }
404    
405        void setMemberReader(MemberReader memberReader) {
406            this.memberReader = memberReader;
407        }
408    
409        MemberReader getMemberReader() {
410            return memberReader;
411        }
412    
413        public Map<String, Annotation> getAnnotationMap() {
414            return annotationMap;
415        }
416    
417        RolapLevel newMeasuresLevel() {
418            RolapLevel level =
419                new RolapLevel(
420                    this,
421                    "MeasuresLevel",
422                    null,
423                    null,
424                    this.levels.length,
425                    null,
426                    null,
427                    null,
428                    null,
429                    null,
430                    null,
431                    null,
432                    RolapProperty.emptyArray,
433                    0,
434                    null,
435                    RolapLevel.HideMemberCondition.Never,
436                    LevelType.Regular,
437                    "",
438                    Collections.<String, Annotation>emptyMap());
439            this.levels = Util.append(this.levels, level);
440            return level;
441        }
442    
443        /**
444         * If this hierarchy has precisely one table, returns that table;
445         * if this hierarchy has no table, return the cube's fact-table;
446         * otherwise, returns null.
447         */
448        MondrianDef.Relation getUniqueTable() {
449            if (relation instanceof MondrianDef.Relation) {
450                return (MondrianDef.Relation) relation;
451            } else if (relation instanceof MondrianDef.Join) {
452                return null;
453            } else {
454                throw Util.newInternal(
455                    "hierarchy's relation is a " + relation.getClass());
456            }
457        }
458    
459        boolean tableExists(String tableName) {
460            return (relation != null) && getTable(tableName, relation) != null;
461        }
462    
463        MondrianDef.Relation getTable(String tableName) {
464            return relation == null ? null : getTable(tableName, relation);
465        }
466    
467        private static MondrianDef.Relation getTable(
468            String tableName,
469            MondrianDef.RelationOrJoin relationOrJoin)
470        {
471            if (relationOrJoin instanceof MondrianDef.Relation) {
472                MondrianDef.Relation relation =
473                    (MondrianDef.Relation) relationOrJoin;
474                if (relation.getAlias().equals(tableName)) {
475                    return relation;
476                } else {
477                    return null;
478                }
479            } else {
480                MondrianDef.Join join = (MondrianDef.Join) relationOrJoin;
481                MondrianDef.Relation rel = getTable(tableName, join.left);
482                if (rel != null) {
483                    return rel;
484                }
485                return getTable(tableName, join.right);
486            }
487        }
488    
489        public RolapSchema getRolapSchema() {
490            return (RolapSchema) dimension.getSchema();
491        }
492    
493        public MondrianDef.RelationOrJoin getRelation() {
494            return relation;
495        }
496    
497        public MondrianDef.Hierarchy getXmlHierarchy() {
498            return xmlHierarchy;
499        }
500    
501        public Member getDefaultMember() {
502            // use lazy initialization to get around bootstrap issues
503            if (defaultMember == null) {
504                List<RolapMember> rootMembers = memberReader.getRootMembers();
505                final SchemaReader schemaReader =
506                    getRolapSchema().getSchemaReader();
507                List<RolapMember> calcMemberList =
508                    Util.cast(schemaReader.getCalculatedMembers(getLevels()[0]));
509                for (RolapMember rootMember
510                    : UnionIterator.over(rootMembers, calcMemberList))
511                {
512                    if (rootMember.isHidden()) {
513                        continue;
514                    }
515                    // Note: We require that the root member is not a hidden member
516                    // of a ragged hierarchy, but we do not require that it is
517                    // visible. In particular, if a cube contains no explicit
518                    // measures, the default measure will be the implicitly defined
519                    // [Fact Count] measure, which happens to be non-visible.
520                    defaultMember = rootMember;
521                    break;
522                }
523                if (defaultMember == null) {
524                    throw MondrianResource.instance().InvalidHierarchyCondition.ex(
525                        this.getUniqueName());
526                }
527            }
528            return defaultMember;
529        }
530    
531        public Member getNullMember() {
532            // use lazy initialization to get around bootstrap issues
533            if (nullMember == null) {
534                nullMember = new RolapNullMember(nullLevel);
535            }
536            return nullMember;
537        }
538    
539        /**
540         * Returns the 'all' member.
541         */
542        public RolapMember getAllMember() {
543            return allMember;
544        }
545    
546        public Member createMember(
547            Member parent,
548            Level level,
549            String name,
550            Formula formula)
551        {
552            if (formula == null) {
553                return new RolapMemberBase(
554                    (RolapMember) parent, (RolapLevel) level, name);
555            } else if (level.getDimension().isMeasures()) {
556                return new RolapCalculatedMeasure(
557                    (RolapMember) parent, (RolapLevel) level, name, formula);
558            } else {
559                return new RolapCalculatedMember(
560                    (RolapMember) parent, (RolapLevel) level, name, formula);
561            }
562        }
563    
564        String getAlias() {
565            return getName();
566        }
567    
568        /**
569         * Returns the name of the source hierarchy, if this hierarchy is shared,
570         * otherwise null.
571         *
572         * <p>If this hierarchy is a public -- that is, it belongs to a dimension
573         * which is a usage of a shared dimension -- then
574         * <code>sharedHierarchyName</code> holds the unique name of the shared
575         * hierarchy; otherwise it is null.
576         *
577         * <p> Suppose this hierarchy is "Weekly" in the dimension "Order Date" of
578         * cube "Sales", and that "Order Date" is a usage of the "Time"
579         * dimension. Then <code>sharedHierarchyName</code> will be
580         * "[Time].[Weekly]".
581         */
582        public String getSharedHierarchyName() {
583            return sharedHierarchyName;
584        }
585    
586        /**
587         * Adds to the FROM clause of the query the tables necessary to access the
588         * members of this hierarchy in an inverse join order, used with agg tables.
589         * If <code>expression</code> is not null, adds the tables necessary to
590         * compute that expression.
591         *
592         * <p> This method is idempotent: if you call it more than once, it only
593         * adds the table(s) to the FROM clause once.
594         *
595         * @param query Query to add the hierarchy to
596         * @param expression Level to qualify up to; if null, qualifies up to the
597         *    topmost ('all') expression, which may require more columns and more
598         *    joins
599         */
600        void addToFromInverse(SqlQuery query, MondrianDef.Expression expression) {
601            if (relation == null) {
602                throw Util.newError(
603                    "cannot add hierarchy " + getUniqueName()
604                    + " to query: it does not have a <Table>, <View> or <Join>");
605            }
606            final boolean failIfExists = false;
607            MondrianDef.RelationOrJoin subRelation = relation;
608            if (relation instanceof MondrianDef.Join) {
609                if (expression != null) {
610                    subRelation =
611                        relationSubsetInverse(relation, expression.getTableAlias());
612                }
613            }
614            query.addFrom(subRelation, null, failIfExists);
615        }
616    
617        /**
618         * Adds to the FROM clause of the query the tables necessary to access the
619         * members of this hierarchy. If <code>expression</code> is not null, adds
620         * the tables necessary to compute that expression.
621         *
622         * <p> This method is idempotent: if you call it more than once, it only
623         * adds the table(s) to the FROM clause once.
624         *
625         * @param query Query to add the hierarchy to
626         * @param expression Level to qualify up to; if null, qualifies up to the
627         *    topmost ('all') expression, which may require more columns and more
628         *    joins
629         */
630        void addToFrom(SqlQuery query, MondrianDef.Expression expression) {
631            if (relation == null) {
632                throw Util.newError(
633                    "cannot add hierarchy " + getUniqueName()
634                    + " to query: it does not have a <Table>, <View> or <Join>");
635            }
636            final boolean failIfExists = false;
637            MondrianDef.RelationOrJoin subRelation = relation;
638            if (relation instanceof MondrianDef.Join) {
639                if (expression != null) {
640                    // Suppose relation is
641                    //   (((A join B) join C) join D)
642                    // and the fact table is
643                    //   F
644                    // and our expression uses C. We want to make the expression
645                    //   F left join ((A join B) join C).
646                    // Search for the smallest subset of the relation which
647                    // uses C.
648                    subRelation =
649                        relationSubset(relation, expression.getTableAlias());
650                }
651            }
652            query.addFrom(subRelation, null, failIfExists);
653        }
654    
655        /**
656         * Adds a table to the FROM clause of the query.
657         * If <code>table</code> is not null, adds the table. Otherwise, add the
658         * relation on which this hierarchy is based on.
659         *
660         * <p> This method is idempotent: if you call it more than once, it only
661         * adds the table(s) to the FROM clause once.
662         *
663         * @param query Query to add the hierarchy to
664         * @param table table to add to the query
665         */
666        void addToFrom(SqlQuery query, RolapStar.Table table) {
667            if (getRelation() == null) {
668                throw Util.newError(
669                    "cannot add hierarchy " + getUniqueName()
670                    + " to query: it does not have a <Table>, <View> or <Join>");
671            }
672            final boolean failIfExists = false;
673            MondrianDef.RelationOrJoin subRelation = null;
674            if (table != null) {
675                // Suppose relation is
676                //   (((A join B) join C) join D)
677                // and the fact table is
678                //   F
679                // and the table to add is C. We want to make the expression
680                //   F left join ((A join B) join C).
681                // Search for the smallest subset of the relation which
682                // joins with C.
683                subRelation = lookupRelationSubset(getRelation(), table);
684            }
685    
686            if (subRelation == null) {
687                // If no table is found or specified, add the entire base relation.
688                subRelation = getRelation();
689            }
690    
691            boolean tableAdded = query.addFrom(subRelation, null, failIfExists);
692            if (tableAdded && table != null) {
693                RolapStar.Condition joinCondition = table.getJoinCondition();
694                if (joinCondition != null) {
695                    query.addWhere(joinCondition);
696                }
697            }
698        }
699    
700        /**
701         * Returns the smallest subset of <code>relation</code> which contains
702         * the relation <code>alias</code>, or null if these is no relation with
703         * such an alias, in inverse join order, used for agg tables.
704         *
705         * @param relation the relation in which to look for table by its alias
706         * @param alias table alias to search for
707         * @return the smallest containing relation or null if no matching table
708         * is found in <code>relation</code>
709         */
710        private static MondrianDef.RelationOrJoin relationSubsetInverse(
711            MondrianDef.RelationOrJoin relation,
712            String alias)
713        {
714            if (relation instanceof MondrianDef.Relation) {
715                MondrianDef.Relation table =
716                    (MondrianDef.Relation) relation;
717                return table.getAlias().equals(alias)
718                    ? relation
719                    : null;
720    
721            } else if (relation instanceof MondrianDef.Join) {
722                MondrianDef.Join join = (MondrianDef.Join) relation;
723                MondrianDef.RelationOrJoin leftRelation =
724                    relationSubsetInverse(join.left, alias);
725                return (leftRelation == null)
726                    ? relationSubsetInverse(join.right, alias)
727                    : join;
728    
729            } else {
730                throw Util.newInternal("bad relation type " + relation);
731            }
732        }
733    
734        /**
735         * Returns the smallest subset of <code>relation</code> which contains
736         * the relation <code>alias</code>, or null if these is no relation with
737         * such an alias.
738         * @param relation the relation in which to look for table by its alias
739         * @param alias table alias to search for
740         * @return the smallest containing relation or null if no matching table
741         * is found in <code>relation</code>
742         */
743        private static MondrianDef.RelationOrJoin relationSubset(
744            MondrianDef.RelationOrJoin relation,
745            String alias)
746        {
747            if (relation instanceof MondrianDef.Relation) {
748                MondrianDef.Relation table =
749                    (MondrianDef.Relation) relation;
750                return table.getAlias().equals(alias)
751                    ? relation
752                    : null;
753    
754            } else if (relation instanceof MondrianDef.Join) {
755                MondrianDef.Join join = (MondrianDef.Join) relation;
756                MondrianDef.RelationOrJoin rightRelation =
757                    relationSubset(join.right, alias);
758                return (rightRelation == null)
759                    ? relationSubset(join.left, alias)
760                    : join;
761    
762            } else {
763                throw Util.newInternal("bad relation type " + relation);
764            }
765        }
766    
767        /**
768         * Returns the smallest subset of <code>relation</code> which contains
769         * the table <code>targetTable</code>, or null if the targetTable is not
770         * one of the joining table in <code>relation</code>.
771         *
772         * @param relation the relation in which to look for targetTable
773         * @param targetTable table to add to the query
774         * @return the smallest containing relation or null if no matching table
775         * is found in <code>relation</code>
776         */
777        private static MondrianDef.RelationOrJoin lookupRelationSubset(
778            MondrianDef.RelationOrJoin relation,
779            RolapStar.Table targetTable)
780        {
781            if (relation instanceof MondrianDef.Table) {
782                MondrianDef.Table table = (MondrianDef.Table) relation;
783                if (table.name.equals(targetTable.getTableName())) {
784                    return relation;
785                } else {
786                    // Not the same table if table names are different
787                    return null;
788                }
789            } else if (relation instanceof MondrianDef.Join) {
790                // Search inside relation, starting from the rightmost table,
791                // and move left along the join chain.
792                MondrianDef.Join join = (MondrianDef.Join) relation;
793                MondrianDef.RelationOrJoin rightRelation =
794                    lookupRelationSubset(join.right, targetTable);
795                if (rightRelation == null) {
796                    // Keep searching left.
797                    return lookupRelationSubset(
798                        join.left, targetTable);
799                } else {
800                    // Found a match.
801                    return join;
802                }
803            }
804            return null;
805        }
806    
807        /**
808         * Creates a member reader which enforces the access-control profile of
809         * <code>role</code>.
810         *
811         * <p>This method may not be efficient, so the caller should take care
812         * not to call it too often. A cache is a good idea.
813         *
814         * @param role Role
815         * @return Member reader that implements access control
816         *
817         * @pre role != null
818         * @post return != null
819         */
820        MemberReader createMemberReader(Role role) {
821            final Access access = role.getAccess(this);
822            switch (access) {
823            case NONE:
824                role.getAccess(this); // todo: remove
825                throw Util.newInternal(
826                    "Illegal access to members of hierarchy " + this);
827            case ALL:
828                return (isRagged())
829                    ? new RestrictedMemberReader(getMemberReader(), role)
830                    : getMemberReader();
831    
832            case CUSTOM:
833                final Role.HierarchyAccess hierarchyAccess =
834                    role.getAccessDetails(this);
835                final Role.RollupPolicy rollupPolicy =
836                    hierarchyAccess.getRollupPolicy();
837                final NumericType returnType = new NumericType();
838                switch (rollupPolicy) {
839                case FULL:
840                    return new RestrictedMemberReader(getMemberReader(), role);
841                case PARTIAL:
842                    Type memberType1 =
843                        new mondrian.olap.type.MemberType(
844                            getDimension(),
845                            this,
846                            null,
847                            null);
848                    SetType setType = new SetType(memberType1);
849                    ListCalc listCalc =
850                        new AbstractMemberListCalc(
851                            new DummyExp(setType), new Calc[0])
852                        {
853                            public List<Member> evaluateMemberList(
854                                Evaluator evaluator)
855                            {
856                                return FunUtil.getNonEmptyMemberChildren(
857                                    evaluator,
858                                    ((RolapEvaluator) evaluator).getExpanding());
859                            }
860    
861                            public boolean dependsOn(Hierarchy hierarchy) {
862                                return true;
863                            }
864                        };
865                    final Calc partialCalc =
866                        new LimitedRollupAggregateCalc(returnType, listCalc);
867    
868                    final Exp partialExp =
869                        new ResolvedFunCall(
870                            new FunDefBase("$x", "x", "In") {
871                                public Calc compileCall(
872                                    ResolvedFunCall call,
873                                    ExpCompiler compiler)
874                                {
875                                    return partialCalc;
876                                }
877    
878                                public void unparse(Exp[] args, PrintWriter pw) {
879                                    pw.print("$RollupAccessibleChildren()");
880                                }
881                            },
882                            new Exp[0],
883                            returnType);
884                    return new LimitedRollupSubstitutingMemberReader(
885                        getMemberReader(), role, hierarchyAccess, partialExp);
886    
887                case HIDDEN:
888                    Exp hiddenExp =
889                        new ResolvedFunCall(
890                            new FunDefBase("$x", "x", "In") {
891                                public Calc compileCall(
892                                    ResolvedFunCall call, ExpCompiler compiler)
893                                {
894                                    return new ConstantCalc(returnType, null);
895                                }
896    
897                                public void unparse(Exp[] args, PrintWriter pw) {
898                                    pw.print("$RollupAccessibleChildren()");
899                                }
900                            },
901                            new Exp[0],
902                            returnType);
903                    return new LimitedRollupSubstitutingMemberReader(
904                        getMemberReader(), role, hierarchyAccess, hiddenExp);
905                default:
906                    throw Util.unexpected(rollupPolicy);
907                }
908            default:
909                throw Util.badValue(access);
910            }
911        }
912    
913        /**
914         * A hierarchy is ragged if it contains one or more levels with hidden
915         * members.
916         */
917        public boolean isRagged() {
918            for (Level level : levels) {
919                if (((RolapLevel) level).getHideMemberCondition()
920                    != RolapLevel.HideMemberCondition.Never)
921                {
922                    return true;
923                }
924            }
925            return false;
926        }
927    
928        /**
929         * Returns an expression which will compute a member's value by aggregating
930         * its children.
931         *
932         * <p>It is efficient to share one expression between all calculated members
933         * in a parent-child hierarchy, so we only need need to validate the
934         * expression once.
935         */
936        synchronized Exp getAggregateChildrenExpression() {
937            if (aggregateChildrenExpression == null) {
938                UnresolvedFunCall fc = new UnresolvedFunCall(
939                    "$AggregateChildren",
940                    Syntax.Internal,
941                    new Exp[] {new HierarchyExpr(this)});
942                Validator validator =
943                        Util.createSimpleValidator(BuiltinFunTable.instance());
944                aggregateChildrenExpression = fc.accept(validator);
945            }
946            return aggregateChildrenExpression;
947        }
948    
949        /**
950         * Builds a dimension which maps onto a table holding the transitive
951         * closure of the relationship for this parent-child level.
952         *
953         * <p>This method is triggered by the
954         * {@link mondrian.olap.MondrianDef.Closure} element
955         * in a schema, and is only meaningful for a parent-child hierarchy.
956         *
957         * <p>When a Schema contains a parent-child Hierarchy that has an
958         * associated closure table, Mondrian creates a parallel internal
959         * Hierarchy, called a "closed peer", that refers to the closure table.
960         * This is indicated in the schema at the level of a Level, by including a
961         * Closure element. The closure table represents
962         * the transitive closure of the parent-child relationship.
963         *
964         * <p>The peer dimension, with its single hierarchy, and 3 levels (all,
965         * closure, item) really 'belong to' the parent-child level. If a single
966         * hierarchy had two parent-child levels (however unlikely this might be)
967         * then each level would have its own auxiliary dimension.
968         *
969         * <p>For example, in the demo schema the [HR].[Employee] dimension
970         * contains a parent-child hierarchy:
971         *
972         * <pre>
973         * &lt;Dimension name="Employees" foreignKey="employee_id"&gt;
974         *   &lt;Hierarchy hasAll="true" allMemberName="All Employees"
975         *         primaryKey="employee_id"&gt;
976         *     &lt;Table name="employee"/&gt;
977         *     &lt;Level name="Employee Id" type="Numeric" uniqueMembers="true"
978         *            column="employee_id" parentColumn="supervisor_id"
979         *            nameColumn="full_name" nullParentValue="0"&gt;
980         *       &lt;Closure parentColumn="supervisor_id"
981         *                   childColumn="employee_id"&gt;
982         *          &lt;Table name="employee_closure"/&gt;
983         *       &lt;/Closure&gt;
984         *       ...
985         * </pre>
986         * The internal closed peer Hierarchy has this structure:
987         * <pre>
988         * &lt;Dimension name="Employees" foreignKey="employee_id"&gt;
989         *     ...
990         *     &lt;Hierarchy name="Employees$Closure"
991         *         hasAll="true" allMemberName="All Employees"
992         *         primaryKey="employee_id" primaryKeyTable="employee_closure"&gt;
993         *       &lt;Join leftKey="supervisor_id" rightKey="employee_id"&gt;
994         *         &lt;Table name="employee_closure"/&gt;
995         *         &lt;Table name="employee"/&gt;
996         *       &lt;/Join&gt;
997         *       &lt;Level name="Closure"  type="Numeric" uniqueMembers="false"
998         *           table="employee_closure" column="supervisor_id"/&gt;
999         *       &lt;Level name="Employee" type="Numeric" uniqueMembers="true"
1000         *           table="employee_closure" column="employee_id"/&gt;
1001         *     &lt;/Hierarchy&gt;
1002         * </pre>
1003         *
1004         * <p>Note that the original Level with the Closure produces two Levels in
1005         * the closed peer Hierarchy: a simple peer (Employee) and a closed peer
1006         * (Closure).
1007         *
1008         * @param src a parent-child Level that has a Closure clause
1009         * @param clos a Closure clause
1010         * @return the closed peer Level in the closed peer Hierarchy
1011         */
1012        RolapDimension createClosedPeerDimension(
1013            RolapLevel src,
1014            MondrianDef.Closure clos,
1015            MondrianDef.CubeDimension xmlDimension)
1016        {
1017            // REVIEW (mb): What about attribute primaryKeyTable?
1018    
1019            // Create a peer dimension.
1020            RolapDimension peerDimension = new RolapDimension(
1021                dimension.getSchema(),
1022                dimension.getName() + "$Closure",
1023                null,
1024                "Closure dimension for parent-child hierarchy " + getName(),
1025                DimensionType.StandardDimension,
1026                dimension.isHighCardinality(),
1027                Collections.<String, Annotation>emptyMap());
1028    
1029            // Create a peer hierarchy.
1030            RolapHierarchy peerHier = peerDimension.newHierarchy(null, true, this);
1031            peerHier.allMemberName = getAllMemberName();
1032            peerHier.allMember = (RolapMemberBase) getAllMember();
1033            peerHier.allLevelName = getAllLevelName();
1034            peerHier.sharedHierarchyName = getSharedHierarchyName();
1035            MondrianDef.Join join = new MondrianDef.Join();
1036            peerHier.relation = join;
1037            join.left = clos.table;         // the closure table
1038            join.leftKey = clos.parentColumn;
1039            join.right = relation;     // the unclosed base table
1040            join.rightKey = clos.childColumn;
1041    
1042            // Create the upper level.
1043            // This represents all groups of descendants. For example, in the
1044            // Employee closure hierarchy, this level has a row for every employee.
1045            int index = peerHier.levels.length;
1046            int flags = src.getFlags() &~ RolapLevel.FLAG_UNIQUE;
1047            MondrianDef.Expression keyExp =
1048                new MondrianDef.Column(clos.table.name, clos.parentColumn);
1049    
1050            RolapLevel level =
1051                new RolapLevel(
1052                    peerHier, "Closure", caption, description, index++,
1053                    keyExp, null, null, null,
1054                    null, null,  // no longer a parent-child hierarchy
1055                    null,
1056                    RolapProperty.emptyArray,
1057                    flags | RolapLevel.FLAG_UNIQUE,
1058                    src.getDatatype(),
1059                    src.getHideMemberCondition(),
1060                    src.getLevelType(),
1061                    "",
1062                    Collections.<String, Annotation>emptyMap());
1063            peerHier.levels = Util.append(peerHier.levels, level);
1064    
1065            // Create lower level.
1066            // This represents individual items. For example, in the Employee
1067            // closure hierarchy, this level has a row for every direct and
1068            // indirect report of every employee (which is more than the number
1069            // of employees).
1070            flags = src.getFlags() | RolapLevel.FLAG_UNIQUE;
1071            keyExp = new MondrianDef.Column(clos.table.name, clos.childColumn);
1072            RolapLevel sublevel = new RolapLevel(
1073                peerHier,
1074                "Item",
1075                null,
1076                null,
1077                index++,
1078                keyExp,
1079                null,
1080                null,
1081                null,
1082                null,
1083                null,  // no longer a parent-child hierarchy
1084                null,
1085                RolapProperty.emptyArray,
1086                flags,
1087                src.getDatatype(),
1088                src.getHideMemberCondition(),
1089                src.getLevelType(),
1090                "",
1091                Collections.<String, Annotation>emptyMap());
1092            peerHier.levels = Util.append(peerHier.levels, sublevel);
1093    
1094            return peerDimension;
1095        }
1096    
1097        /**
1098         * Sets default member of this Hierarchy.
1099         *
1100         * @param defaultMember Default member
1101         */
1102        public void setDefaultMember(Member defaultMember) {
1103            if (defaultMember != null) {
1104                this.defaultMember = defaultMember;
1105            }
1106        }
1107    
1108    
1109        /**
1110         * <p>Gets "unique key level name" attribute of this Hierarchy, if set.
1111         * If set, this property indicates that all level properties are
1112         * functionally dependent (invariant) on their associated levels,
1113         * and that the set of levels from the root to the named level (inclusive)
1114         * effectively defines an alternate key.</p>
1115         *
1116         * <p>This allows the GROUP BY to be eliminated from associated queries.</p>
1117         *
1118         * @return the name of the "unique key" level, or null if not specified
1119         */
1120        public String getUniqueKeyLevelName() {
1121            return uniqueKeyLevelName;
1122        }
1123    
1124        /**
1125         * Returns the ordinal of this hierarchy in its cube.
1126         *
1127         * <p>Temporarily defined against RolapHierarchy; will be moved to
1128         * RolapCubeHierarchy as soon as the measures hierarchy is a
1129         * RolapCubeHierarchy.
1130         *
1131         * @return Ordinal of this hierarchy in its cube
1132         */
1133        public int getOrdinalInCube() {
1134            // This is temporary to verify that all calls to this method are for
1135            // the measures hierarchy. For all other hierarchies, the context will
1136            // be a RolapCubeHierarchy.
1137            //
1138            // In particular, if this method is called from
1139            // RolapEvaluator.setContext, the caller of that method should have
1140            // passed in a RolapCubeMember, not a RolapMember.
1141            assert dimension.isMeasures();
1142            return 0;
1143        }
1144    
1145    
1146        /**
1147         * A <code>RolapNullMember</code> is the null member of its hierarchy.
1148         * Every hierarchy has precisely one. They are yielded by operations such as
1149         * <code>[Gender].[All].ParentMember</code>. Null members are usually
1150         * omitted from sets (in particular, in the set constructor operator "{ ...
1151         * }".
1152         */
1153        static class RolapNullMember extends RolapMemberBase {
1154            RolapNullMember(final RolapLevel level) {
1155                super(
1156                    null,
1157                    level,
1158                    null,
1159                    RolapUtil.mdxNullLiteral(),
1160                    MemberType.NULL);
1161                assert level != null;
1162            }
1163        }
1164    
1165        /**
1166         * Calculated member which is also a measure (that is, a member of the
1167         * [Measures] dimension).
1168         */
1169        protected static class RolapCalculatedMeasure
1170            extends RolapCalculatedMember
1171            implements RolapMeasure
1172        {
1173            private CellFormatter cellFormatter;
1174    
1175            public RolapCalculatedMeasure(
1176                RolapMember parent, RolapLevel level, String name, Formula formula)
1177            {
1178                super(parent, level, name, formula);
1179            }
1180    
1181            public synchronized void setProperty(String name, Object value) {
1182                if (name.equals(Property.CELL_FORMATTER.getName())) {
1183                    String cellFormatterClass = (String) value;
1184                    try {
1185                        this.cellFormatter =
1186                            RolapCube.getCellFormatter(cellFormatterClass);
1187                    } catch (Exception e) {
1188                        throw MondrianResource.instance().CellFormatterLoadFailed
1189                            .ex(
1190                                cellFormatterClass, getUniqueName(), e);
1191                    }
1192                }
1193                super.setProperty(name, value);
1194            }
1195    
1196            public CellFormatter getFormatter() {
1197                return cellFormatter;
1198            }
1199        }
1200    
1201        /**
1202         * Substitute for a member in a hierarchy whose rollup policy is 'partial'
1203         * or 'hidden'. The member is calculated using an expression which
1204         * aggregates only visible descendants.
1205         *
1206         * <p>Note that this class extends RolapCubeMember only because other code
1207         * expects that all members in a RolapCubeHierarchy are RolapCubeMembers.
1208         * As part of {@link mondrian.util.Bug#BugSegregateRolapCubeMemberFixed},
1209         * maybe make {@link mondrian.rolap.RolapCubeMember} an interface.
1210         *
1211         * @see mondrian.olap.Role.RollupPolicy
1212         */
1213        public static class LimitedRollupMember extends RolapCubeMember {
1214            public final RolapMember member;
1215            private final Exp exp;
1216    
1217            LimitedRollupMember(
1218                RolapCubeMember member,
1219                Exp exp)
1220            {
1221                super(
1222                    member.getParentMember(),
1223                    member.getRolapMember(),
1224                    member.getLevel());
1225                assert !(member instanceof LimitedRollupMember);
1226                this.member = member;
1227                this.exp = exp;
1228            }
1229    
1230            public boolean equals(Object o) {
1231                return o instanceof LimitedRollupMember
1232                    && ((LimitedRollupMember) o).member.equals(member);
1233            }
1234    
1235            public Exp getExpression() {
1236                return exp;
1237            }
1238    
1239            protected boolean computeCalculated(final MemberType memberType) {
1240                return true;
1241            }
1242    
1243            public boolean isCalculated() {
1244                return false;
1245            }
1246    
1247            public boolean isEvaluated() {
1248                return true;
1249            }
1250        }
1251    
1252        /**
1253         * Member reader which wraps a hierarchy's member reader, and if the
1254         * role has limited access to the hierarchy, replaces members with
1255         * dummy members which evaluate to the sum of only the accessible children.
1256         */
1257        private static class LimitedRollupSubstitutingMemberReader
1258            extends SubstitutingMemberReader
1259        {
1260            private final Role.HierarchyAccess hierarchyAccess;
1261            private final Exp exp;
1262    
1263            /**
1264             * Creates a LimitedRollupSubstitutingMemberReader.
1265             *
1266             * @param memberReader Underlying member reader
1267             * @param role Role to enforce
1268             * @param hierarchyAccess Access this role has to the hierarchy
1269             * @param exp Expression for hidden member
1270             */
1271            public LimitedRollupSubstitutingMemberReader(
1272                MemberReader memberReader,
1273                Role role,
1274                Role.HierarchyAccess hierarchyAccess,
1275                Exp exp)
1276            {
1277                super(
1278                    new RestrictedMemberReader(
1279                        memberReader, role));
1280                this.hierarchyAccess = hierarchyAccess;
1281                this.exp = exp;
1282            }
1283    
1284            @Override
1285            public RolapMember substitute(final RolapMember member) {
1286                if (member != null
1287                    && (hierarchyAccess.getAccess(member) == Access.CUSTOM
1288                    || hierarchyAccess.hasInaccessibleDescendants(member)))
1289                {
1290                    // Member is visible, but at least one of its
1291                    // descendants is not.
1292                    return new LimitedRollupMember((RolapCubeMember)member, exp);
1293                } else {
1294                    // No need to substitute. Member and all of its
1295                    // descendants are accessible. Total for member
1296                    // is same as for FULL policy.
1297                    return member;
1298                }
1299            }
1300    
1301            @Override
1302            public RolapMember desubstitute(RolapMember member) {
1303                if (member instanceof LimitedRollupMember) {
1304                    return ((LimitedRollupMember) member).member;
1305                } else {
1306                    return member;
1307                }
1308            }
1309        }
1310    
1311        /**
1312         * Compiled expression that computes rollup over a set of visible children.
1313         * The {@code listCalc} expression determines that list of children.
1314         */
1315        private static class LimitedRollupAggregateCalc
1316            extends AggregateFunDef.AggregateCalc
1317        {
1318            public LimitedRollupAggregateCalc(Type returnType, ListCalc listCalc) {
1319                super(
1320                    new DummyExp(returnType),
1321                    listCalc,
1322                    new ValueCalc(new DummyExp(returnType)));
1323            }
1324        }
1325    
1326        /**
1327         * Dummy element that acts as a namespace for resolving member names within
1328         * shared hierarchies. Acts like a cube that has a single child, the
1329         * hierarchy in question.
1330         */
1331        private class DummyElement implements OlapElement {
1332            public String getUniqueName() {
1333                throw new UnsupportedOperationException();
1334            }
1335    
1336            public String getName() {
1337                return "$";
1338            }
1339    
1340            public String getDescription() {
1341                throw new UnsupportedOperationException();
1342            }
1343    
1344            public OlapElement lookupChild(
1345                SchemaReader schemaReader,
1346                Id.Segment s,
1347                MatchType matchType)
1348            {
1349                if (Util.equalName(s.name, dimension.getName())) {
1350                    return dimension;
1351                }
1352                // Archaic form <dimension>.<hierarchy>, e.g. [Time.Weekly].[1997]
1353                if (Util.equalName(s.name, dimension.getName() + "." + subName)) {
1354                    return RolapHierarchy.this;
1355                }
1356                return null;
1357            }
1358    
1359            public String getQualifiedName() {
1360                throw new UnsupportedOperationException();
1361            }
1362    
1363            public String getCaption() {
1364                throw new UnsupportedOperationException();
1365            }
1366    
1367            public Hierarchy getHierarchy() {
1368                throw new UnsupportedOperationException();
1369            }
1370    
1371            public Dimension getDimension() {
1372                throw new UnsupportedOperationException();
1373            }
1374        }
1375    }
1376    
1377    // End RolapHierarchy.java
1378