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) 2003-2005 Julian Hyde
008// Copyright (C) 2005-2009 Pentaho
009// All Rights Reserved.
010*/
011package mondrian.olap.fun;
012
013import mondrian.mdx.*;
014import mondrian.olap.*;
015import mondrian.olap.type.*;
016
017import java.util.ArrayList;
018import java.util.List;
019
020/**
021 * A <code>ParameterFunDef</code> is a pseudo-function describing calls to
022 * <code>Parameter</code> and <code>ParamRef</code> functions. It exists only
023 * fleetingly, and is then converted into a {@link mondrian.olap.Parameter}.
024 * For internal use only.
025 *
026 * @author jhyde
027 * @since Feb 14, 2003
028 */
029public class ParameterFunDef extends FunDefBase {
030    public final String parameterName;
031    private final Type type;
032    public final Exp exp;
033    public final String parameterDescription;
034
035    ParameterFunDef(
036        FunDef funDef,
037        String parameterName,
038        Type type,
039        int returnCategory,
040        Exp exp,
041        String description)
042    {
043        super(
044            funDef.getName(),
045            funDef.getSignature(),
046            funDef.getDescription(),
047            funDef.getSyntax(),
048            returnCategory,
049            funDef.getParameterCategories());
050        assertPrecondition(
051            getName().equals("Parameter")
052            || getName().equals("ParamRef"));
053        this.parameterName = parameterName;
054        this.type = type;
055        this.exp = exp;
056        this.parameterDescription = description;
057    }
058
059    public Exp createCall(Validator validator, Exp[] args) {
060        Parameter parameter = validator.createOrLookupParam(
061            this.getName().equals("Parameter"),
062            parameterName, type, exp, parameterDescription);
063        return new ParameterExpr(parameter);
064    }
065
066    public Type getResultType(Validator validator, Exp[] args) {
067        return type;
068    }
069
070    private static boolean isConstant(Exp typeArg) {
071        if (typeArg instanceof LevelExpr) {
072            // e.g. "[Time].[Quarter]"
073            return true;
074        }
075        if (typeArg instanceof HierarchyExpr) {
076            // e.g. "[Time].[By Week]"
077            return true;
078        }
079        if (typeArg instanceof DimensionExpr) {
080            // e.g. "[Time]"
081            return true;
082        }
083        if (typeArg instanceof FunCall) {
084            // e.g. "[Time].CurrentMember.Hierarchy". They probably wrote
085            // "[Time]", and the automatic type conversion did the rest.
086            FunCall hierarchyCall = (FunCall) typeArg;
087            if (hierarchyCall.getFunName().equals("Hierarchy")
088                && hierarchyCall.getArgCount() > 0
089                && hierarchyCall.getArg(0) instanceof FunCall)
090            {
091                FunCall currentMemberCall = (FunCall) hierarchyCall.getArg(0);
092                if (currentMemberCall.getFunName().equals("CurrentMember")
093                    && currentMemberCall.getArgCount() > 0
094                    && currentMemberCall.getArg(0) instanceof DimensionExpr)
095                {
096                    return true;
097                }
098            }
099        }
100        return false;
101    }
102
103    public static String getParameterName(Exp[] args) {
104        if (args[0] instanceof Literal
105            && args[0].getCategory() == Category.String)
106        {
107            return (String) ((Literal) args[0]).getValue();
108        } else {
109            throw Util.newInternal("Parameter name must be a string constant");
110        }
111    }
112
113    /**
114     * Returns an approximate type for a parameter, based upon the 1'th
115     * argument. Does not use the default value expression, so this method
116     * can safely be used before the expression has been validated.
117     */
118    public static Type getParameterType(Exp[] args) {
119        if (args[1] instanceof Id) {
120            Id id = (Id) args[1];
121            String[] names = id.toStringArray();
122            if (names.length == 1) {
123                final String name = names[0];
124                if (name.equals("NUMERIC")) {
125                    return new NumericType();
126                }
127                if (name.equals("STRING")) {
128                    return new StringType();
129                }
130            }
131        } else if (args[1] instanceof Literal) {
132            final Literal literal = (Literal) args[1];
133            if (literal.getValue().equals("NUMERIC")) {
134                return new NumericType();
135            } else if (literal.getValue().equals("STRING")) {
136                return new StringType();
137            }
138        } else if (args[1] instanceof MemberExpr) {
139            return new MemberType(null, null, null, null);
140        }
141        return new StringType();
142    }
143
144    /**
145     * Resolves calls to the <code>Parameter</code> MDX function.
146     */
147    public static class ParameterResolver extends MultiResolver {
148        private static final String[] SIGNATURES = {
149            // Parameter(string const, symbol, string[, string const]): string
150            "fS#yS#", "fS#yS",
151            // Parameter(string const, symbol, numeric[, string const]): numeric
152            "fn#yn#", "fn#yn",
153            // Parameter(string const, hierarchy constant, member[, string
154            // const[, symbol]]): member
155            "fm#hm#", "fm#hm",
156            // Parameter(string const, hierarchy constant, set[, string
157            // const]): set
158            "fx#hx#", "fx#hx",
159        };
160
161        public ParameterResolver() {
162            super(
163                "Parameter",
164                "Parameter(<Name>, <Type>, <DefaultValue>, <Description>, <Set>)",
165                "Returns default value of parameter.",
166                SIGNATURES);
167        }
168
169        public String[] getReservedWords() {
170            return new String[]{"NUMERIC", "STRING"};
171        }
172
173        protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
174            String parameterName = getParameterName(args);
175            Exp typeArg = args[1];
176            int category;
177            Type type = typeArg.getType();
178            switch (typeArg.getCategory()) {
179            case Category.Dimension:
180            case Category.Hierarchy:
181            case Category.Level:
182                Dimension dimension = type.getDimension();
183                if (!isConstant(typeArg)) {
184                    throw newEvalException(
185                        dummyFunDef,
186                        "Invalid parameter '" + parameterName
187                        + "'. Type must be a NUMERIC, STRING, or a dimension, "
188                        + "hierarchy or level");
189                }
190                if (dimension == null) {
191                    throw newEvalException(
192                        dummyFunDef,
193                        "Invalid dimension for parameter '"
194                        + parameterName + "'");
195                }
196                type =
197                    new MemberType(
198                        type.getDimension(),
199                        type.getHierarchy(),
200                        type.getLevel(),
201                        null);
202                category = Category.Member;
203                break;
204
205            case Category.Symbol:
206                String s = (String) ((Literal) typeArg).getValue();
207                if (s.equalsIgnoreCase("NUMERIC")) {
208                    category = Category.Numeric;
209                    type = new NumericType();
210                    break;
211                } else if (s.equalsIgnoreCase("STRING")) {
212                    category = Category.String;
213                    type = new StringType();
214                    break;
215                }
216                // fall through and throw error
217            default:
218                // Error is internal because the function call has already been
219                // type-checked.
220                throw newEvalException(
221                    dummyFunDef,
222                    "Invalid type for parameter '" + parameterName
223                    + "'; expecting NUMERIC, STRING or a hierarchy");
224            }
225
226            // Default value
227            Exp exp = args[2];
228            Validator validator =
229                createSimpleValidator(BuiltinFunTable.instance());
230            final List<Conversion> conversionList = new ArrayList<Conversion>();
231            String typeName = Category.instance.getName(category).toUpperCase();
232            if (!validator.canConvert(2, exp, category, conversionList)) {
233                throw newEvalException(
234                    dummyFunDef,
235                    "Default value of parameter '" + parameterName
236                    + "' is inconsistent with its type, " + typeName);
237            }
238            if (exp.getCategory() == Category.Set
239                && category == Category.Member)
240            {
241                // Default value is a set; take this an indication that
242                // the type is 'set of <member type>'.
243                type = new SetType(type);
244            }
245            if (category == Category.Member) {
246                Type expType = exp.getType();
247                if (expType instanceof SetType) {
248                    expType = ((SetType) expType).getElementType();
249                }
250                if (distinctFrom(type.getDimension(), expType.getDimension())
251                    || distinctFrom(type.getHierarchy(), expType.getHierarchy())
252                    || distinctFrom(type.getLevel(), expType.getLevel()))
253                {
254                    throw newEvalException(
255                        dummyFunDef,
256                        "Default value of parameter '" + parameterName
257                        + "' is not consistent with the parameter type '"
258                        + type);
259                }
260            }
261
262            String parameterDescription = null;
263            if (args.length > 3) {
264                if (args[3] instanceof Literal
265                    && args[3].getCategory() == Category.String)
266                {
267                    parameterDescription =
268                        (String) ((Literal) args[3]).getValue();
269                } else {
270                    throw newEvalException(
271                        dummyFunDef,
272                        "Description of parameter '" + parameterName
273                        + "' must be a string constant");
274                }
275            }
276
277            return new ParameterFunDef(
278                dummyFunDef, parameterName, type, category,
279                exp, parameterDescription);
280        }
281
282        private static <T> boolean distinctFrom(T t1, T t2) {
283            return t1 != null
284               && t2 != null
285               && !t1.equals(t2);
286        }
287    }
288
289    /**
290     * Resolves calls to the <code>ParamRef</code> MDX function.
291     */
292    public static class ParamRefResolver extends MultiResolver {
293        public ParamRefResolver() {
294            super(
295                "ParamRef",
296                "ParamRef(<Name>)",
297                "Returns the current value of this parameter. If it is null, returns the default value.",
298                new String[]{"fv#"});
299        }
300
301        protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) {
302            String parameterName = getParameterName(args);
303            return new ParameterFunDef(
304                dummyFunDef, parameterName, null, Category.Unknown, null,
305                null);
306        }
307    }
308}
309
310// End ParameterFunDef.java