001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/RolapCubeLevel.java#8 $
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    // wgorman, 19 October 2007
012    */
013    package mondrian.rolap;
014    
015    import mondrian.olap.*;
016    import mondrian.rolap.agg.CellRequest;
017    import mondrian.rolap.agg.MemberColumnPredicate;
018    import mondrian.rolap.agg.MemberTuplePredicate;
019    import mondrian.rolap.agg.RangeColumnPredicate;
020    import mondrian.rolap.agg.ValueColumnPredicate;
021    
022    /**
023     * RolapCubeLevel wraps a RolapLevel for a specific Cube.
024     *
025     * @author Will Gorman (wgorman@pentaho.org)
026     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapCubeLevel.java#8 $
027     */
028    public class RolapCubeLevel extends RolapLevel {
029    
030        private final RolapLevel rolapLevel;
031        private RolapStar.Column starKeyColumn = null;
032    
033        protected LevelReader levelReader;
034    
035        public RolapCubeLevel(RolapLevel level, RolapCubeHierarchy hierarchy) {
036            super(hierarchy, level.getDepth(), level.getName(), level.getKeyExp(),
037                    level.getNameExp(), level.getCaptionExp(),
038                    level.getOrdinalExp(), level.getParentExp(),
039                    level.getNullParentValue(), null, level.getProperties(),
040                    level.getFlags(), level.getDatatype(),
041                    level.getHideMemberCondition(),
042                    level.getLevelType(), "" + level.getApproxRowCount());
043    
044            this.rolapLevel = level;
045            MondrianDef.RelationOrJoin hierarchyRel = hierarchy.getRelation();
046            keyExp = convertExpression(level.getKeyExp(), hierarchyRel);
047            nameExp = convertExpression(level.getNameExp(), hierarchyRel);
048            captionExp = convertExpression(level.getCaptionExp(), hierarchyRel);
049            ordinalExp = convertExpression(level.getOrdinalExp(), hierarchyRel);
050            parentExp = convertExpression(level.getParentExp(), hierarchyRel);
051        }
052    
053        void init(MondrianDef.CubeDimension xmlDimension) {
054            if (isAll()) {
055                this.levelReader = new AllLevelReaderImpl();
056            } else if (getLevelType() == LevelType.Null) {
057                this.levelReader = new NullLevelReader();
058            } else if (rolapLevel.xmlClosure != null) {
059                RolapDimension dimension =
060                    (RolapDimension)rolapLevel.getClosedPeer()
061                                        .getHierarchy().getDimension();
062    
063                RolapCubeDimension cubeDimension =
064                    new RolapCubeDimension(
065                            getCube(), dimension, xmlDimension,
066                            getDimension().getName() + "$Closure",
067                            getHierarchy().getDimension().getOrdinal(),
068                            getHierarchy().getDimension().isHighCardinality());
069    
070                /*
071                RME HACK
072                  WG: Note that the reason for registering this usage is so that
073                  when registerDimension is called, the hierarchy is registered
074                  successfully to the star.  This type of hack will go away once
075                  HierarchyUsage is phased out
076                */
077                getCube().createUsage(
078                        (RolapCubeHierarchy)cubeDimension.getHierarchies()[0],
079                        xmlDimension);
080    
081                cubeDimension.init(xmlDimension);
082                getCube().registerDimension(cubeDimension);
083                RolapCubeLevel closedPeer =
084                    (RolapCubeLevel) cubeDimension.getHierarchies()[0].getLevels()[1];
085    
086                this.levelReader = new ParentChildLevelReaderImpl(closedPeer);
087            } else {
088                this.levelReader = new RegularLevelReader();
089            }
090        }
091    
092        /**
093         * Converts an expression to new aliases if necessary.
094         *
095         * @param exp the expression to convert
096         * @param rel the parent relation
097         * @return returns the converted expression
098         */
099        private MondrianDef.Expression convertExpression(
100            MondrianDef.Expression exp,
101            MondrianDef.RelationOrJoin rel)
102        {
103            if (getHierarchy().isUsingCubeFact()) {
104                // no conversion necessary
105                return exp;
106            } else if (exp == null || rel == null) {
107                return null;
108            } else if (exp instanceof MondrianDef.Column) {
109                MondrianDef.Column col = (MondrianDef.Column)exp;
110                if (rel instanceof MondrianDef.Table) {
111                    return new MondrianDef.Column(
112                        ((MondrianDef.Table) rel).getAlias(),
113                        col.getColumnName());
114                } else if (rel instanceof MondrianDef.Join
115                            || rel instanceof MondrianDef.Relation) {
116                    // need to determine correct name of alias for this level.
117                    // this may be defined in level
118                    // col.table
119                    String alias = getHierarchy().lookupAlias(col.getTableAlias());
120                    return new MondrianDef.Column(alias, col.getColumnName());
121                }
122            } else if (exp instanceof MondrianDef.ExpressionView) {
123                // this is a limitation, in the future, we may need
124                // to replace the table name in the sql provided
125                // with the new aliased name
126                return exp;
127            }
128            throw new RuntimeException("conversion of Class " + exp.getClass() +
129                                        " unsupported at this time");
130        }
131    
132        public void setStarKeyColumn(RolapStar.Column column) {
133            starKeyColumn = column;
134        }
135    
136        /**
137         * This is the RolapStar.Column that is related to this RolapCubeLevel
138         *
139         * @return the RolapStar.Column related to this RolapCubeLevel
140         */
141        public RolapStar.Column getStarKeyColumn() {
142            return starKeyColumn;
143        }
144    
145        LevelReader getLevelReader() {
146            return levelReader;
147        }
148    
149        /**
150         * this method returns the RolapStar.Column if non-virtual,
151         * if virtual, find the base cube level and return it's
152         * column
153         *
154         * @param baseCube the base cube for the specificed virtual level
155         * @return the RolapStar.Column related to this RolapCubeLevel
156         */
157        public RolapStar.Column getBaseStarKeyColumn(RolapCube baseCube) {
158            RolapStar.Column column = null;
159            if (getCube().isVirtual() && baseCube != null) {
160                RolapCubeLevel lvl = baseCube.findBaseCubeLevel(this);
161                if (lvl != null) {
162                    column = lvl.getStarKeyColumn();
163                }
164            } else {
165                column = getStarKeyColumn();
166            }
167            return column;
168        }
169    
170        /**
171         * Returns the (non virtual) cube this level belongs to.
172         *
173         * @return cube
174         */
175        public RolapCube getCube() {
176            return getHierarchy().getDimension().getCube();
177        }
178    
179        // override with stricter return type
180        public final RolapCubeHierarchy getHierarchy() {
181            return (RolapCubeHierarchy) super.getHierarchy();
182        }
183    
184        // override with stricter return type
185        public final RolapCubeLevel getChildLevel() {
186            return (RolapCubeLevel) super.getChildLevel();
187        }
188    
189        // override with stricter return type
190        public RolapCubeLevel getParentLevel() {
191            return (RolapCubeLevel) super.getParentLevel();
192        }
193    
194        public RolapLevel getRolapLevel() {
195            return rolapLevel;
196        }
197    
198        public boolean equals(RolapCubeLevel level) {
199            // verify the levels are part of the same hierarchy
200            return super.equals(level)
201                    && getCube().equals(level.getCube());
202        }
203    
204        boolean hasClosedPeer() {
205            return rolapLevel.hasClosedPeer();
206        }
207    
208        public MemberFormatter getMemberFormatter() {
209            return rolapLevel.getMemberFormatter();
210        }
211    
212    
213    
214        /**
215         * Encapsulation of the difference between levels in terms of how
216         * constraints are generated. There are implementations for 'all' levels,
217         * the 'null' level, parent-child levels and regular levels.
218         */
219        interface LevelReader {
220    
221            /**
222             * Adds constraints to a cell request for a member of this level.
223             *
224             * @param member Member to be constrained
225             * @param baseCube base cube if virtual level
226             * @param request Request to be constrained
227             *
228             * @return true if request is unsatisfiable (e.g. if the member is the
229             * null member)
230             */
231            boolean constrainRequest(
232                RolapCubeMember member,
233                RolapCube baseCube,
234                CellRequest request);
235    
236            /**
237             * Adds constraints to a cache region for a member of this level.
238             *
239             * @param predicate Predicate
240             * @param baseCube base cube if virtual level
241             * @param cacheRegion Cache region to be constrained
242             */
243            void constrainRegion(
244                StarColumnPredicate predicate,
245                RolapCube baseCube,
246                RolapCacheRegion cacheRegion);
247        }
248    
249        /**
250         * Level reader for a regular level.
251         */
252        class RegularLevelReader implements LevelReader {
253            public boolean constrainRequest(
254                RolapCubeMember member,
255                RolapCube baseCube,
256                CellRequest request)
257            {
258                assert member.getLevel() == RolapCubeLevel.this;
259                if (member.getKey() == null) {
260                    if (member == member.getHierarchy().getNullMember()) {
261                        // cannot form a request if one of the members is null
262                        return true;
263                    } else {
264                        throw Util.newInternal("why is key null?");
265                    }
266                }
267    
268                RolapStar.Column column = getBaseStarKeyColumn(baseCube);
269    
270                if (column == null) {
271                    // This hierarchy is not one which qualifies the starMeasure
272                    // (this happens in virtual cubes). The starMeasure only has
273                    // a value for the 'all' member of the hierarchy (or for the
274                    // default member if the hierarchy has no 'all' member)
275                    return member != hierarchy.getDefaultMember() ||
276                        hierarchy.hasAll();
277                }
278    
279                final StarColumnPredicate predicate;
280                if (member.isCalculated()) {
281                    predicate = null;
282                } else {
283                    predicate = false ? new MemberColumnPredicate(column, member) :
284                        new ValueColumnPredicate(column, member.getKey());
285                }
286    
287                // use the member as constraint; this will give us some
288                //  optimization potential
289                request.addConstrainedColumn(column, predicate);
290    
291                if (request.extendedContext &&
292                    getNameExp() != null)
293                {
294                    final RolapStar.Column nameColumn = column.getNameColumn();
295    
296                    assert nameColumn != null;
297                    request.addConstrainedColumn(nameColumn, null);
298                }
299    
300                if (member.isCalculated()) {
301                    return false;
302                }
303    
304                // If member is unique without reference to its parent,
305                // no further constraint is required.
306                if (isUnique()) {
307                    return false;
308                }
309    
310                // Constrain the parent member, if any.
311                RolapCubeMember parent = member.getParentMember();
312                while (true) {
313                    if (parent == null) {
314                        return false;
315                    }
316                    RolapCubeLevel level = parent.getLevel();
317                    final LevelReader levelReader = level.levelReader;
318                    if (levelReader == this) {
319                        // We are looking at a parent in a parent-child hierarchy,
320                        // for example, we have moved from Fred to Fred's boss,
321                        // Wilma. We don't want to include Wilma's key in the
322                        // request.
323                        parent = parent.getParentMember();
324                        continue;
325                    }
326                    return levelReader.constrainRequest(
327                        parent, baseCube, request);
328                }
329            }
330    
331            public void constrainRegion(
332                StarColumnPredicate predicate,
333                RolapCube baseCube,
334                RolapCacheRegion cacheRegion)
335            {
336                RolapStar.Column column = getBaseStarKeyColumn(baseCube);
337    
338                if (column == null) {
339                    // This hierarchy is not one which qualifies the starMeasure
340                    // (this happens in virtual cubes). The starMeasure only has
341                    // a value for the 'all' member of the hierarchy (or for the
342                    // default member if the hierarchy has no 'all' member)
343                    return;
344                }
345    
346                if (predicate instanceof MemberColumnPredicate) {
347                    MemberColumnPredicate memberColumnPredicate =
348                        (MemberColumnPredicate) predicate;
349                    RolapMember member = memberColumnPredicate.getMember();
350                    assert member.getLevel() == RolapCubeLevel.this;
351                    assert !member.isCalculated();
352                    assert memberColumnPredicate.getMember().getKey() != null;
353                    assert !member.isNull();
354    
355                    // use the member as constraint, this will give us some
356                    //  optimization potential
357                    cacheRegion.addPredicate(column, predicate);
358                    return;
359                } else if (predicate instanceof RangeColumnPredicate) {
360                    RangeColumnPredicate rangeColumnPredicate =
361                        (RangeColumnPredicate) predicate;
362                    final ValueColumnPredicate lowerBound =
363                        rangeColumnPredicate.getLowerBound();
364                    RolapMember lowerMember;
365                    if (lowerBound == null) {
366                        lowerMember = null;
367                    } else if (lowerBound instanceof MemberColumnPredicate) {
368                        MemberColumnPredicate memberColumnPredicate =
369                            (MemberColumnPredicate) lowerBound;
370                        lowerMember = memberColumnPredicate.getMember();
371                    } else {
372                        throw new UnsupportedOperationException();
373                    }
374                    final ValueColumnPredicate upperBound =
375                        rangeColumnPredicate.getUpperBound();
376                    RolapMember upperMember;
377                    if (upperBound == null) {
378                        upperMember = null;
379                    } else if (upperBound instanceof MemberColumnPredicate) {
380                        MemberColumnPredicate memberColumnPredicate =
381                            (MemberColumnPredicate) upperBound;
382                        upperMember = memberColumnPredicate.getMember();
383                    } else {
384                        throw new UnsupportedOperationException();
385                    }
386                    MemberTuplePredicate predicate2 =
387                        new MemberTuplePredicate(
388                            baseCube,
389                            lowerMember,
390                            !rangeColumnPredicate.getLowerInclusive(),
391                            upperMember,
392                            !rangeColumnPredicate.getUpperInclusive());
393                    // use the member as constraint, this will give us some
394                    //  optimization potential
395                    cacheRegion.addPredicate(predicate2);
396                    return;
397                }
398    
399                // Unknown type of constraint.
400                throw new UnsupportedOperationException();
401            }
402        }
403    
404        /**
405         * Level reader for a parent-child level which has a closed peer level.
406         */
407        class ParentChildLevelReaderImpl extends RegularLevelReader {
408            /**
409             * For a parent-child hierarchy with a closure provided by the schema,
410             * the equivalent level in the closed hierarchy; otherwise null.
411             */
412            protected final RolapCubeLevel closedPeer;
413    
414            ParentChildLevelReaderImpl(RolapCubeLevel closedPeer) {
415                this.closedPeer = closedPeer;
416            }
417    
418            public boolean constrainRequest(
419                RolapCubeMember member,
420                RolapCube baseCube,
421                CellRequest request)
422            {
423    
424                // Replace a parent/child level by its closed equivalent, when
425                // available; this is always valid, and improves performance by
426                // enabling the database to compute aggregates.
427                if (member.getDataMember() == null) {
428                    // Member has no data member because it IS the data
429                    // member of a parent-child hierarchy member. Leave
430                    // it be. We don't want to aggregate.
431                    return super.constrainRequest(member, baseCube, request);
432                } else if (request.drillThrough) {
433                    member = (RolapCubeMember) member.getDataMember();
434                    return super.constrainRequest(member, baseCube, request);
435                } else {
436                    RolapCubeLevel level = closedPeer;
437                    final RolapMember wrappedAllMember =
438                        (RolapMember)rolapLevel.getClosedPeer().getHierarchy()
439                                .getDefaultMember();
440    
441    
442                    final RolapCubeMember allMember = (RolapCubeMember)
443                            level.getHierarchy().getDefaultMember();
444                    assert allMember.isAll();
445    
446                    // isn't creating a member on the fly a bad idea?
447                    RolapMember wrappedMember =
448                            new RolapMember(
449                                wrappedAllMember,
450                                rolapLevel.getClosedPeer(),
451                                member.getKey());
452                    member =
453                        new RolapCubeMember(
454                            allMember,
455                            wrappedMember,
456                            closedPeer,
457                            RolapCubeLevel.this.getCube());
458    
459                    return level.getLevelReader().constrainRequest(
460                        member, baseCube, request);
461                }
462            }
463    
464            public void constrainRegion(
465                StarColumnPredicate predicate,
466                RolapCube baseCube,
467                RolapCacheRegion cacheRegion)
468            {
469                throw new UnsupportedOperationException();
470            }
471        }
472    
473        /**
474         * Level reader for the level which contains the 'all' member.
475         */
476        static class AllLevelReaderImpl implements LevelReader {
477            public boolean constrainRequest(
478                RolapCubeMember member,
479                RolapCube baseCube,
480                CellRequest request)
481            {
482                // We don't need to apply any constraints.
483                return false;
484            }
485    
486            public void constrainRegion(
487                StarColumnPredicate predicate,
488                RolapCube baseCube,
489                RolapCacheRegion cacheRegion)
490            {
491                // We don't need to apply any constraints.
492            }
493        }
494    
495        /**
496         * Level reader for the level which contains the null member.
497         */
498        static class NullLevelReader implements LevelReader {
499            public boolean constrainRequest(
500                RolapCubeMember member,
501                RolapCube baseCube,
502                CellRequest request)
503            {
504                return true;
505            }
506    
507            public void constrainRegion(
508                StarColumnPredicate predicate,
509                RolapCube baseCube,
510                RolapCacheRegion cacheRegion)
511            {
512            }
513        }
514    
515    }
516    
517    // End RolapCubeLevel.java