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) 2005-2005 Julian Hyde 008// Copyright (C) 2005-2012 Pentaho 009// All Rights Reserved. 010*/ 011package mondrian.olap.fun; 012 013import mondrian.calc.*; 014import mondrian.calc.impl.*; 015import mondrian.mdx.ResolvedFunCall; 016import mondrian.olap.*; 017import mondrian.olap.type.*; 018import mondrian.spi.UserDefinedFunction; 019 020import java.util.*; 021 022/** 023 * Resolver for user-defined functions. 024 * 025 * @author jhyde 026 * @since 2.0 027 */ 028public class UdfResolver implements Resolver { 029 private final UdfFactory factory; 030 private final UserDefinedFunction udf; 031 032 private static final String[] emptyStringArray = new String[0]; 033 034 public UdfResolver(UdfFactory factory) { 035 this.factory = factory; 036 this.udf = factory.create(); 037 } 038 039 public String getName() { 040 return udf.getName(); 041 } 042 043 public String getDescription() { 044 return udf.getDescription(); 045 } 046 047 public String getSignature() { 048 Type[] parameterTypes = udf.getParameterTypes(); 049 int[] parameterCategories = new int[parameterTypes.length]; 050 for (int i = 0; i < parameterCategories.length; i++) { 051 parameterCategories[i] = TypeUtil.typeToCategory(parameterTypes[i]); 052 } 053 Type returnType = udf.getReturnType(parameterTypes); 054 int returnCategory = TypeUtil.typeToCategory(returnType); 055 return getSyntax().getSignature( 056 getName(), 057 returnCategory, 058 parameterCategories); 059 } 060 061 public Syntax getSyntax() { 062 return udf.getSyntax(); 063 } 064 065 public FunDef getFunDef() { 066 Type[] parameterTypes = udf.getParameterTypes(); 067 int[] parameterCategories = new int[parameterTypes.length]; 068 for (int i = 0; i < parameterCategories.length; i++) { 069 parameterCategories[i] = TypeUtil.typeToCategory(parameterTypes[i]); 070 } 071 Type returnType = udf.getReturnType(parameterTypes); 072 return new UdfFunDef(parameterCategories, returnType); 073 } 074 075 public FunDef resolve( 076 Exp[] args, 077 Validator validator, 078 List<Conversion> conversions) 079 { 080 final Type[] parameterTypes = udf.getParameterTypes(); 081 if (args.length != parameterTypes.length) { 082 return null; 083 } 084 int[] parameterCategories = new int[parameterTypes.length]; 085 Type[] castArgTypes = new Type[parameterTypes.length]; 086 for (int i = 0; i < parameterTypes.length; i++) { 087 Type parameterType = parameterTypes[i]; 088 final Exp arg = args[i]; 089 final Type argType = arg.getType(); 090 final int parameterCategory = 091 TypeUtil.typeToCategory(parameterType); 092 if (!validator.canConvert( 093 i, arg, parameterCategory, conversions)) 094 { 095 return null; 096 } 097 parameterCategories[i] = parameterCategory; 098 if (!parameterType.equals(argType)) { 099 castArgTypes[i] = 100 FunDefBase.castType(argType, parameterCategory); 101 } 102 } 103 final Type returnType = udf.getReturnType(castArgTypes); 104 return new UdfFunDef(parameterCategories, returnType); 105 } 106 107 public boolean requiresExpression(int k) { 108 return false; 109 } 110 111 public String[] getReservedWords() { 112 final String[] reservedWords = udf.getReservedWords(); 113 return reservedWords == null ? emptyStringArray : reservedWords; 114 } 115 116 /** 117 * Adapter which converts a {@link UserDefinedFunction} into a 118 * {@link FunDef}. 119 */ 120 private class UdfFunDef extends FunDefBase { 121 private Type returnType; 122 123 public UdfFunDef(int[] parameterCategories, Type returnType) { 124 super( 125 UdfResolver.this, 126 TypeUtil.typeToCategory(returnType), 127 parameterCategories); 128 this.returnType = returnType; 129 } 130 131 public Type getResultType(Validator validator, Exp[] args) { 132 return returnType; 133 } 134 135 public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { 136 final Exp[] args = call.getArgs(); 137 Calc[] calcs = new Calc[args.length]; 138 UserDefinedFunction.Argument[] expCalcs = 139 new UserDefinedFunction.Argument[args.length]; 140 for (int i = 0; i < args.length; i++) { 141 Exp arg = args[i]; 142 final Calc calc = calcs[i] = compiler.compileAs( 143 arg, 144 castType(arg.getType(), parameterCategories[i]), 145 ResultStyle.ANY_LIST); 146 calcs[i] = calc; 147 final Calc scalarCalc = compiler.compileScalar(arg, true); 148 final ListCalc listCalc; 149 final IterCalc iterCalc; 150 if (arg.getType() instanceof SetType) { 151 listCalc = compiler.compileList(arg, true); 152 iterCalc = compiler.compileIter(arg); 153 } else { 154 listCalc = null; 155 iterCalc = null; 156 } 157 expCalcs[i] = new CalcExp(calc, scalarCalc, listCalc, iterCalc); 158 } 159 160 // Create a new instance of the UDF, because some UDFs use member 161 // variables as state. 162 UserDefinedFunction udf2 = factory.create(); 163 if (call.getType() instanceof SetType) { 164 return new ListCalcImpl(call, calcs, udf2, expCalcs); 165 } else { 166 return new ScalarCalcImpl(call, calcs, udf2, expCalcs); 167 } 168 } 169 } 170 171 /** 172 * Expression that evaluates a scalar user-defined function. 173 */ 174 private static class ScalarCalcImpl extends GenericCalc { 175 private final Calc[] calcs; 176 private final UserDefinedFunction udf; 177 private final UserDefinedFunction.Argument[] args; 178 179 public ScalarCalcImpl( 180 ResolvedFunCall call, 181 Calc[] calcs, 182 UserDefinedFunction udf, 183 UserDefinedFunction.Argument[] args) 184 { 185 super(call); 186 this.calcs = calcs; 187 this.udf = udf; 188 this.args = args; 189 } 190 191 public Calc[] getCalcs() { 192 return calcs; 193 } 194 195 public Object evaluate(Evaluator evaluator) { 196 try { 197 return udf.execute(evaluator, args); 198 } catch (Exception e) { 199 return FunUtil.newEvalException( 200 "Exception while executing function " + udf.getName(), 201 e); 202 } 203 } 204 205 public boolean dependsOn(Hierarchy hierarchy) { 206 // Be pessimistic. This effectively disables expression caching. 207 return true; 208 } 209 } 210 211 /** 212 * Expression that evaluates a list user-defined function. 213 */ 214 private static class ListCalcImpl extends AbstractListCalc { 215 private final UserDefinedFunction udf; 216 private final UserDefinedFunction.Argument[] args; 217 218 public ListCalcImpl( 219 ResolvedFunCall call, 220 Calc[] calcs, 221 UserDefinedFunction udf, 222 UserDefinedFunction.Argument[] args) 223 { 224 super(call, calcs); 225 this.udf = udf; 226 this.args = args; 227 } 228 229 public TupleList evaluateList(Evaluator evaluator) { 230 final List list = (List) udf.execute(evaluator, args); 231 232 // If arity is 1, assume they have returned a list of members. 233 // For other arity, assume a list of member arrays. 234 if (getType().getArity() == 1) { 235 //noinspection unchecked 236 return new UnaryTupleList((List<Member>) list); 237 } else { 238 // Use an adapter to make a list of member arrays look like 239 // a list of members laid end-to-end. 240 final int arity = getType().getArity(); 241 //noinspection unchecked 242 final List<Member[]> memberArrayList = (List<Member[]>) list; 243 return new ListTupleList( 244 arity, 245 new AbstractList<Member>() { 246 @Override 247 public Member get(int index) { 248 return memberArrayList.get(index / arity) 249 [index % arity]; 250 } 251 252 @Override 253 public int size() { 254 return memberArrayList.size() * arity; 255 } 256 } 257 ); 258 } 259 } 260 261 @Override 262 public boolean dependsOn(Hierarchy hierarchy) { 263 // Be pessimistic. This effectively disables expression caching. 264 return true; 265 } 266 } 267 268 /** 269 * Wrapper around a {@link Calc} to make it appear as an {@link Exp}. 270 * Only the {@link #evaluate(mondrian.olap.Evaluator)} 271 * and {@link #evaluateScalar(mondrian.olap.Evaluator)} methods are 272 * supported. 273 */ 274 private static class CalcExp implements UserDefinedFunction.Argument { 275 private final Calc calc; 276 private final Calc scalarCalc; 277 private final IterCalc iterCalc; 278 private final ListCalc listCalc; 279 280 /** 281 * Creates a CalcExp. 282 * 283 * @param calc Compiled expression 284 * @param scalarCalc Compiled expression that evaluates to a scalar 285 * @param listCalc Compiled expression that evaluates an MDX set to 286 * a java list 287 * @param iterCalc Compiled expression that evaluates an MDX set to 288 */ 289 public CalcExp( 290 Calc calc, 291 Calc scalarCalc, 292 ListCalc listCalc, 293 IterCalc iterCalc) 294 { 295 this.calc = calc; 296 this.scalarCalc = scalarCalc; 297 this.listCalc = listCalc; 298 this.iterCalc = iterCalc; 299 } 300 301 public Type getType() { 302 return calc.getType(); 303 } 304 305 public Object evaluate(Evaluator evaluator) { 306 return adapt(calc.evaluate(evaluator)); 307 } 308 309 public Object evaluateScalar(Evaluator evaluator) { 310 return scalarCalc.evaluate(evaluator); 311 } 312 313 public List evaluateList(Evaluator eval) { 314 if (listCalc == null) { 315 throw new RuntimeException("Expression is not a set"); 316 } 317 return adaptList(listCalc.evaluateList(eval)); 318 } 319 320 public Iterable evaluateIterable(Evaluator eval) { 321 if (iterCalc == null) { 322 throw new RuntimeException("Expression is not a set"); 323 } 324 return adaptIterable(iterCalc.evaluateIterable(eval)); 325 } 326 327 /** 328 * Adapts the output of {@link TupleList} and {@link TupleIterable} 329 * calculator expressions to the old style, that returned either members 330 * or arrays of members. 331 * 332 * @param o Output of calc 333 * @return Output in new format (lists and iterables over lists of 334 * members) 335 */ 336 private Object adapt(Object o) { 337 if (o instanceof TupleIterable) { 338 return adaptIterable((TupleIterable) o); 339 } 340 return o; 341 } 342 343 private List adaptList(final TupleList tupleList) { 344 // List is required to be mutable -- so make a copy. 345 if (tupleList.getArity() == 1) { 346 return new ArrayList<Member>(tupleList.slice(0)); 347 } else { 348 return new ArrayList<Member[]>( 349 TupleCollections.asMemberArrayList(tupleList)); 350 } 351 } 352 353 private Iterable adaptIterable(final TupleIterable tupleIterable) { 354 if (tupleIterable instanceof TupleList) { 355 return adaptList((TupleList) tupleIterable); 356 } 357 if (tupleIterable.getArity() == 1) { 358 return tupleIterable.slice(0); 359 } else { 360 return TupleCollections.asMemberArrayIterable(tupleIterable); 361 } 362 } 363 } 364 365 /** 366 * Factory for {@link UserDefinedFunction}. 367 * 368 * <p>This factory is required because a user-defined function is allowed 369 * to store state in itself. Therefore it is unsanitary to use the same 370 * UDF in the function table for validation and runtime. In the function 371 * table there is a factory. We use one instance of instance of the UDF to 372 * validate, and create another for the runtime plan.</p> 373 */ 374 public interface UdfFactory { 375 /** 376 * Creates a UDF. 377 * 378 * @return UDF 379 */ 380 UserDefinedFunction create(); 381 } 382 383 /** 384 * Implementation of {@link UdfFactory} that instantiates a given class 385 * using a public default constructor. 386 */ 387 public static class ClassUdfFactory implements UdfResolver.UdfFactory { 388 private final Class<? extends UserDefinedFunction> clazz; 389 private final String name; 390 391 /** 392 * Creates a ClassUdfFactory. 393 * 394 * @param clazz Class to instantiate 395 * @param name Name 396 */ 397 public ClassUdfFactory( 398 Class<? extends UserDefinedFunction> clazz, 399 String name) 400 { 401 this.clazz = clazz; 402 this.name = name; 403 assert clazz != null; 404 } 405 406 public UserDefinedFunction create() { 407 return Util.createUdf(clazz, name); 408 } 409 } 410} 411 412// End UdfResolver.java