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