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