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.resource.MondrianResource;
015import mondrian.spi.Dialect;
016import mondrian.spi.PropertyFormatter;
017import mondrian.spi.impl.Scripts;
018
019import org.apache.log4j.Logger;
020
021import org.olap4j.impl.UnmodifiableArrayMap;
022
023import java.util.*;
024
025/**
026 * <code>RolapLevel</code> implements {@link Level} for a ROLAP database.
027 *
028 * @author jhyde
029 * @since 10 August, 2001
030 */
031public class RolapLevel extends LevelBase {
032
033    private static final Logger LOGGER = Logger.getLogger(RolapLevel.class);
034
035    /**
036     * The column or expression which yields the level's key.
037     */
038    protected MondrianDef.Expression keyExp;
039
040    /**
041     * The column or expression which yields the level's ordinal.
042     */
043    protected MondrianDef.Expression ordinalExp;
044
045    /**
046     * The column or expression which yields the level members' caption.
047     */
048    protected MondrianDef.Expression captionExp;
049
050    private final Dialect.Datatype datatype;
051
052    private final int flags;
053
054    static final int FLAG_ALL = 0x02;
055
056    /**
057     * For SQL generator. Whether values of "column" are unique globally
058     * unique (as opposed to unique only within the context of the parent
059     * member).
060     */
061    static final int FLAG_UNIQUE = 0x04;
062
063    private RolapLevel closedPeerLevel;
064
065    protected RolapProperty[] properties;
066    private final RolapProperty[] inheritedProperties;
067
068    /**
069     * Ths expression which gives the name of members of this level. If null,
070     * members are named using the key expression.
071     */
072    protected MondrianDef.Expression nameExp;
073    /** The expression which joins to the parent member in a parent-child
074     * hierarchy, or null if this is a regular hierarchy. */
075    protected MondrianDef.Expression parentExp;
076    /** Value which indicates a null parent in a parent-child hierarchy. */
077    private final String nullParentValue;
078
079    /** Condition under which members are hidden. */
080    private final HideMemberCondition hideMemberCondition;
081    protected final MondrianDef.Closure xmlClosure;
082    private final Map<String, Annotation> annotationMap;
083    private final SqlStatement.Type internalType; // may be null
084
085    /**
086     * Creates a level.
087     *
088     * @pre parentExp != null || nullParentValue == null
089     * @pre properties != null
090     * @pre levelType != null
091     * @pre hideMemberCondition != null
092     */
093    RolapLevel(
094        RolapHierarchy hierarchy,
095        String name,
096        String caption,
097        boolean visible,
098        String description,
099        int depth,
100        MondrianDef.Expression keyExp,
101        MondrianDef.Expression nameExp,
102        MondrianDef.Expression captionExp,
103        MondrianDef.Expression ordinalExp,
104        MondrianDef.Expression parentExp,
105        String nullParentValue,
106        MondrianDef.Closure xmlClosure,
107        RolapProperty[] properties,
108        int flags,
109        Dialect.Datatype datatype,
110        SqlStatement.Type internalType,
111        HideMemberCondition hideMemberCondition,
112        LevelType levelType,
113        String approxRowCount,
114        Map<String, Annotation> annotationMap)
115    {
116        super(
117            hierarchy, name, caption, visible, description, depth, levelType);
118        assert annotationMap != null;
119        Util.assertPrecondition(properties != null, "properties != null");
120        Util.assertPrecondition(
121            hideMemberCondition != null,
122            "hideMemberCondition != null");
123        Util.assertPrecondition(levelType != null, "levelType != null");
124
125        if (keyExp instanceof MondrianDef.Column) {
126            checkColumn((MondrianDef.Column) keyExp);
127        }
128        this.annotationMap = annotationMap;
129        this.approxRowCount = loadApproxRowCount(approxRowCount);
130        this.flags = flags;
131        this.datatype = datatype;
132        this.keyExp = keyExp;
133        if (nameExp != null) {
134            if (nameExp instanceof MondrianDef.Column) {
135                checkColumn((MondrianDef.Column) nameExp);
136            }
137        }
138        this.nameExp = nameExp;
139        if (captionExp != null) {
140            if (captionExp instanceof MondrianDef.Column) {
141                checkColumn((MondrianDef.Column) captionExp);
142            }
143        }
144        this.captionExp = captionExp;
145        if (ordinalExp != null) {
146            if (ordinalExp instanceof MondrianDef.Column) {
147                checkColumn((MondrianDef.Column) ordinalExp);
148            }
149            this.ordinalExp = ordinalExp;
150        } else {
151            this.ordinalExp = this.keyExp;
152        }
153        if (parentExp instanceof MondrianDef.Column) {
154            checkColumn((MondrianDef.Column) parentExp);
155        }
156        this.parentExp = parentExp;
157        if (parentExp != null) {
158            Util.assertTrue(
159                !isAll(),
160                "'All' level '" + this + "' must not be parent-child");
161            Util.assertTrue(
162                isUnique(),
163                "Parent-child level '" + this
164                + "' must have uniqueMembers=\"true\"");
165        }
166        this.nullParentValue = nullParentValue;
167        Util.assertPrecondition(
168            parentExp != null || nullParentValue == null,
169            "parentExp != null || nullParentValue == null");
170        this.xmlClosure = xmlClosure;
171        for (RolapProperty property : properties) {
172            if (property.getExp() instanceof MondrianDef.Column) {
173                checkColumn((MondrianDef.Column) property.getExp());
174            }
175        }
176        this.properties = properties;
177        List<Property> list = new ArrayList<Property>();
178        for (Level level = this; level != null;
179             level = level.getParentLevel())
180        {
181            final Property[] levelProperties = level.getProperties();
182            for (final Property levelProperty : levelProperties) {
183                Property existingProperty = lookupProperty(
184                    list, levelProperty.getName());
185                if (existingProperty == null) {
186                    list.add(levelProperty);
187                } else if (existingProperty.getType()
188                    != levelProperty.getType())
189                {
190                    throw Util.newError(
191                        "Property " + this.getName() + "."
192                        + levelProperty.getName() + " overrides a "
193                        + "property with the same name but different type");
194                }
195            }
196        }
197        this.inheritedProperties = list.toArray(new RolapProperty[list.size()]);
198
199        Dimension dim = hierarchy.getDimension();
200        if (dim.getDimensionType() == DimensionType.TimeDimension) {
201            if (!levelType.isTime() && !isAll()) {
202                throw MondrianResource.instance()
203                    .NonTimeLevelInTimeHierarchy.ex(getUniqueName());
204            }
205        } else if (dim.getDimensionType() == null) {
206            // there was no dimension type assigned to the dimension
207            // - check later
208        } else {
209            if (levelType.isTime()) {
210                throw MondrianResource.instance()
211                    .TimeLevelInNonTimeHierarchy.ex(getUniqueName());
212            }
213        }
214        this.internalType = internalType;
215        this.hideMemberCondition = hideMemberCondition;
216    }
217
218    public RolapHierarchy getHierarchy() {
219        return (RolapHierarchy) hierarchy;
220    }
221
222    public Map<String, Annotation> getAnnotationMap() {
223        return annotationMap;
224    }
225
226    private int loadApproxRowCount(String approxRowCount) {
227        boolean notNullAndNumeric =
228            approxRowCount != null
229                && approxRowCount.matches("^\\d+$");
230        if (notNullAndNumeric) {
231            return Integer.parseInt(approxRowCount);
232        } else {
233            // if approxRowCount is not set, return MIN_VALUE to indicate
234            return Integer.MIN_VALUE;
235        }
236    }
237
238    protected Logger getLogger() {
239        return LOGGER;
240    }
241
242    String getTableName() {
243        String tableName = null;
244
245        MondrianDef.Expression expr = getKeyExp();
246        if (expr instanceof MondrianDef.Column) {
247            MondrianDef.Column mc = (MondrianDef.Column) expr;
248            tableName = mc.getTableAlias();
249        }
250        return tableName;
251    }
252
253    public MondrianDef.Expression getKeyExp() {
254        return keyExp;
255    }
256
257    MondrianDef.Expression getOrdinalExp() {
258        return ordinalExp;
259    }
260
261    public MondrianDef.Expression getCaptionExp() {
262        return captionExp;
263    }
264
265    public boolean hasCaptionColumn() {
266        return captionExp != null;
267    }
268
269    final int getFlags() {
270        return flags;
271    }
272
273    HideMemberCondition getHideMemberCondition() {
274        return hideMemberCondition;
275    }
276
277    public final boolean isUnique() {
278        return (flags & FLAG_UNIQUE) != 0;
279    }
280
281    final Dialect.Datatype getDatatype() {
282        return datatype;
283    }
284
285    final String getNullParentValue() {
286        return nullParentValue;
287    }
288
289    /**
290     * Returns whether this level is parent-child.
291     */
292    public boolean isParentChild() {
293        return parentExp != null;
294    }
295
296    MondrianDef.Expression getParentExp() {
297        return parentExp;
298    }
299
300    // RME: this has to be public for two of the DrillThroughTest test.
301    public
302    MondrianDef.Expression getNameExp() {
303        return nameExp;
304    }
305
306    private Property lookupProperty(List<Property> list, String propertyName) {
307        for (Property property : list) {
308            if (property.getName().equals(propertyName)) {
309                return property;
310            }
311        }
312        return null;
313    }
314
315    RolapLevel(
316        RolapHierarchy hierarchy,
317        int depth,
318        MondrianDef.Level xmlLevel)
319    {
320        this(
321            hierarchy,
322            xmlLevel.name,
323            xmlLevel.caption,
324            xmlLevel.visible,
325            xmlLevel.description,
326            depth,
327            xmlLevel.getKeyExp(),
328            xmlLevel.getNameExp(),
329            xmlLevel.getCaptionExp(),
330            xmlLevel.getOrdinalExp(),
331            xmlLevel.getParentExp(),
332            xmlLevel.nullParentValue,
333            xmlLevel.closure,
334            createProperties(xmlLevel),
335            (xmlLevel.uniqueMembers ? FLAG_UNIQUE : 0),
336            xmlLevel.getDatatype(),
337            toInternalType(xmlLevel.internalType),
338            HideMemberCondition.valueOf(xmlLevel.hideMemberIf),
339            LevelType.valueOf(
340                xmlLevel.levelType.equals("TimeHalfYear")
341                    ? "TimeHalfYears"
342                    : xmlLevel.levelType),
343            xmlLevel.approxRowCount,
344            RolapHierarchy.createAnnotationMap(xmlLevel.annotations));
345
346        if (!Util.isEmpty(xmlLevel.caption)) {
347            setCaption(xmlLevel.caption);
348        }
349
350        final String memberFormatterClassName;
351        final Scripts.ScriptDefinition scriptDefinition;
352        if (xmlLevel.memberFormatter != null) {
353            memberFormatterClassName = xmlLevel.memberFormatter.className;
354            scriptDefinition =
355                RolapSchema.toScriptDef(xmlLevel.memberFormatter.script);
356        } else {
357            memberFormatterClassName = xmlLevel.formatter;
358            scriptDefinition = null;
359        }
360        if (memberFormatterClassName != null || scriptDefinition != null) {
361            try {
362                memberFormatter =
363                    RolapSchema.getMemberFormatter(
364                        memberFormatterClassName,
365                        scriptDefinition);
366            } catch (Exception e) {
367                throw MondrianResource.instance().MemberFormatterLoadFailed.ex(
368                    xmlLevel.formatter, getUniqueName(), e);
369            }
370        }
371    }
372
373    // helper for constructor
374    private static RolapProperty[] createProperties(
375        MondrianDef.Level xmlLevel)
376    {
377        List<RolapProperty> list = new ArrayList<RolapProperty>();
378        final MondrianDef.Expression nameExp = xmlLevel.getNameExp();
379
380        if (nameExp != null) {
381            list.add(
382                new RolapProperty(
383                    Property.NAME.name, Property.Datatype.TYPE_STRING,
384                    nameExp, null, null, null, true,
385                    Property.NAME.description));
386        }
387        for (int i = 0; i < xmlLevel.properties.length; i++) {
388            MondrianDef.Property xmlProperty = xmlLevel.properties[i];
389
390            final PropertyFormatter formatter;
391            final String propertyFormatterClassName;
392            final Scripts.ScriptDefinition scriptDefinition;
393            if (xmlProperty.propertyFormatter != null) {
394                propertyFormatterClassName =
395                    xmlProperty.propertyFormatter.className;
396                scriptDefinition =
397                    RolapSchema.toScriptDef(
398                        xmlProperty.propertyFormatter.script);
399            } else {
400                propertyFormatterClassName = xmlProperty.formatter;
401                scriptDefinition = null;
402            }
403            if (propertyFormatterClassName != null
404                || scriptDefinition != null)
405            {
406                try {
407                    formatter =
408                        RolapSchema.createPropertyFormatter(
409                            propertyFormatterClassName,
410                            scriptDefinition);
411                } catch (Exception e) {
412                    throw MondrianResource.instance()
413                        .PropertyFormatterLoadFailed.ex(
414                            propertyFormatterClassName, xmlProperty.name, e);
415                }
416            } else {
417                formatter = null;
418            }
419
420            list.add(
421                new RolapProperty(
422                    xmlProperty.name,
423                    convertPropertyTypeNameToCode(xmlProperty.type),
424                    xmlLevel.getPropertyExp(i),
425                    formatter,
426                    xmlProperty.caption,
427                    xmlLevel.properties[i].dependsOnLevelValue,
428                    false,
429                    xmlProperty.description));
430        }
431        return list.toArray(new RolapProperty[list.size()]);
432    }
433
434    private static Property.Datatype convertPropertyTypeNameToCode(
435        String type)
436    {
437        if (type.equals("String")) {
438            return Property.Datatype.TYPE_STRING;
439        } else if (type.equals("Numeric")) {
440            return Property.Datatype.TYPE_NUMERIC;
441        } else if (type.equals("Integer")) {
442            return Property.Datatype.TYPE_NUMERIC;
443        } else if (type.equals("Boolean")) {
444            return Property.Datatype.TYPE_BOOLEAN;
445        } else if (type.equals("Timestamp")) {
446            return Property.Datatype.TYPE_TIMESTAMP;
447        } else if (type.equals("Time")) {
448            return Property.Datatype.TYPE_TIME;
449        } else if (type.equals("Date")) {
450            return Property.Datatype.TYPE_DATE;
451        } else {
452            throw Util.newError("Unknown property type '" + type + "'");
453        }
454    }
455
456    private void checkColumn(MondrianDef.Column nameColumn) {
457        final RolapHierarchy rolapHierarchy = (RolapHierarchy) hierarchy;
458        if (nameColumn.table == null) {
459            final MondrianDef.Relation table = rolapHierarchy.getUniqueTable();
460            if (table == null) {
461                throw Util.newError(
462                    "must specify a table for level " + getUniqueName()
463                    + " because hierarchy has more than one table");
464            }
465            nameColumn.table = table.getAlias();
466        } else {
467            if (!rolapHierarchy.tableExists(nameColumn.table)) {
468                throw Util.newError(
469                    "Table '" + nameColumn.table + "' not found");
470            }
471        }
472    }
473
474    void init(MondrianDef.CubeDimension xmlDimension) {
475        if (xmlClosure != null) {
476            final RolapDimension dimension = ((RolapHierarchy) hierarchy)
477                .createClosedPeerDimension(this, xmlClosure, xmlDimension);
478            closedPeerLevel =
479                    (RolapLevel) dimension.getHierarchies()[0].getLevels()[1];
480        }
481    }
482
483    public final boolean isAll() {
484        return (flags & FLAG_ALL) != 0;
485    }
486
487    public boolean areMembersUnique() {
488        return (depth == 0) || (depth == 1) && hierarchy.hasAll();
489    }
490
491    public String getTableAlias() {
492        return keyExp.getTableAlias();
493    }
494
495    public RolapProperty[] getProperties() {
496        return properties;
497    }
498
499    public Property[] getInheritedProperties() {
500        return inheritedProperties;
501    }
502
503    public int getApproxRowCount() {
504        return approxRowCount;
505    }
506
507    private static final Map<String, SqlStatement.Type> VALUES =
508        UnmodifiableArrayMap.of(
509            "int", SqlStatement.Type.INT,
510            "double", SqlStatement.Type.DOUBLE,
511            "Object", SqlStatement.Type.OBJECT,
512            "String", SqlStatement.Type.STRING,
513            "long", SqlStatement.Type.LONG);
514
515    private static SqlStatement.Type toInternalType(String internalTypeName) {
516        SqlStatement.Type type = VALUES.get(internalTypeName);
517        if (type == null && internalTypeName != null) {
518            throw Util.newError(
519                "Invalid value '" + internalTypeName
520                + "' for attribute 'internalType' of element 'Level'. "
521                + "Valid values are: "
522                + VALUES.keySet());
523        }
524        return type;
525    }
526
527    public SqlStatement.Type getInternalType() {
528        return internalType;
529    }
530
531    /**
532     * Conditions under which a level's members may be hidden (thereby creating
533     * a <dfn>ragged hierarchy</dfn>).
534     */
535    public enum HideMemberCondition {
536        /** A member always appears. */
537        Never,
538
539        /** A member doesn't appear if its name is null or empty. */
540        IfBlankName,
541
542        /** A member appears unless its name matches its parent's. */
543        IfParentsName
544    }
545
546    public OlapElement lookupChild(SchemaReader schemaReader, Id.Segment name) {
547        return lookupChild(schemaReader, name, MatchType.EXACT);
548    }
549
550    public OlapElement lookupChild(
551        SchemaReader schemaReader, Id.Segment name, MatchType matchType)
552    {
553        if (name instanceof Id.KeySegment) {
554            Id.KeySegment keySegment = (Id.KeySegment) name;
555            List<Comparable> keyValues = new ArrayList<Comparable>();
556            for (Id.NameSegment nameSegment : keySegment.getKeyParts()) {
557                final String keyValue = nameSegment.name;
558                if (RolapUtil.mdxNullLiteral().equalsIgnoreCase(keyValue)) {
559                    keyValues.add(RolapUtil.sqlNullValue);
560                } else {
561                    keyValues.add(keyValue);
562                }
563            }
564            final List<MondrianDef.Expression> keyExps = getInheritedKeyExps();
565            if (keyExps.size() != keyValues.size()) {
566                throw Util.newError(
567                    "Wrong number of values in member key; "
568                    + keySegment + " has " + keyValues.size()
569                    + " values, whereas level's key has " + keyExps.size()
570                    + " columns "
571                    + new AbstractList<String>() {
572                        public String get(int index) {
573                            return keyExps.get(index).getGenericExpression();
574                        }
575
576                        public int size() {
577                            return keyExps.size();
578                        }
579                    }
580                    + ".");
581            }
582            return getHierarchy().getMemberReader().getMemberByKey(
583                this, keyValues);
584        }
585        List<Member> levelMembers = schemaReader.getLevelMembers(this, true);
586        if (levelMembers.size() > 0) {
587            Member parent = levelMembers.get(0).getParentMember();
588            return
589                RolapUtil.findBestMemberMatch(
590                    levelMembers,
591                    (RolapMember) parent,
592                    this,
593                    name,
594                    matchType);
595        }
596        return null;
597    }
598
599    private List<MondrianDef.Expression> getInheritedKeyExps() {
600        final List<MondrianDef.Expression> list =
601            new ArrayList<MondrianDef.Expression>();
602        for (RolapLevel x = this;; x = (RolapLevel) x.getParentLevel()) {
603            final MondrianDef.Expression keyExp1 = x.getKeyExp();
604            if (keyExp1 != null) {
605                list.add(keyExp1);
606            }
607            if (x.isUnique()) {
608                break;
609            }
610        }
611        return list;
612    }
613
614    /**
615     * Indicates that level is not ragged and not a parent/child level.
616     */
617    public boolean isSimple() {
618        // most ragged hierarchies are not simple -- see isTooRagged.
619        if (isTooRagged()) {
620            return false;
621        }
622        if (isParentChild()) {
623            return false;
624        }
625        // does not work for measures
626        if (isMeasure()) {
627            return false;
628        }
629        return true;
630    }
631
632    /**
633     * Determines whether the specified level is too ragged for native
634     * evaluation, which is able to handle one special case of a ragged
635     * hierarchy: when the level specified in the query is the leaf level of
636     * the hierarchy and HideMemberCondition for the level is IfBlankName.
637     * This is true even if higher levels of the hierarchy can be hidden
638     * because even in that case the only column that needs to be read is the
639     * column that holds the leaf. IfParentsName can't be handled even at the
640     * leaf level because in the general case we aren't reading the column
641     * that holds the parent. Also, IfBlankName can't be handled for non-leaf
642     * levels because we would have to read the column for the next level
643     * down for members with blank names.
644     *
645     * @return true if the specified level is too ragged for native
646     *         evaluation.
647     */
648    private boolean isTooRagged() {
649        // Is this the special case of raggedness that native evaluation
650        // is able to handle?
651        if (getDepth() == getHierarchy().getLevels().length - 1) {
652            switch (getHideMemberCondition()) {
653            case Never:
654            case IfBlankName:
655                return false;
656            default:
657                return true;
658            }
659        }
660        // Handle the general case in the traditional way.
661        return getHierarchy().isRagged();
662    }
663
664
665    /**
666     * Returns true when the level is part of a parent/child hierarchy and has
667     * an equivalent closed level.
668     */
669    boolean hasClosedPeer() {
670        return closedPeerLevel != null;
671    }
672
673    public RolapLevel getClosedPeer() {
674        return closedPeerLevel;
675    }
676
677    public static RolapLevel lookupLevel(
678        RolapLevel[] levels,
679        String levelName)
680    {
681        for (RolapLevel level : levels) {
682            if (level.getName().equals(levelName)) {
683                return level;
684            }
685        }
686        return null;
687    }
688}
689
690// End RolapLevel.java