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