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) 2009-2009 Pentaho 008// All Rights Reserved. 009*/ 010package mondrian.olap; 011 012import mondrian.mdx.*; 013import mondrian.olap.fun.Resolver; 014import mondrian.olap.type.Type; 015import mondrian.olap.type.TypeUtil; 016import mondrian.resource.MondrianResource; 017import mondrian.util.ArrayStack; 018 019import java.util.*; 020 021/** 022 * Default implementation of {@link mondrian.olap.Validator}. 023 * 024 * <p>Uses a stack to help us guess the type of our parent expression 025 * before we've completely resolved our children -- necessary, 026 * unfortunately, when figuring out whether the "*" operator denotes 027 * multiplication or crossjoin. 028 * 029 * <p>Keeps track of which nodes have already been resolved, so we don't 030 * try to resolve nodes which have already been resolved. (That would not 031 * be wrong, but can cause resolution to be an <code>O(2^N)</code> 032 * operation.) 033 * 034 * <p>The concrete implementing class needs to implement 035 * {@link #getQuery()} and {@link #defineParameter(Parameter)}. 036 * 037 * @author jhyde 038 */ 039abstract class ValidatorImpl implements Validator { 040 protected final ArrayStack<QueryPart> stack = new ArrayStack<QueryPart>(); 041 private final FunTable funTable; 042 private final Map<QueryPart, QueryPart> resolvedNodes = 043 new HashMap<QueryPart, QueryPart>(); 044 private final QueryPart placeHolder = Literal.zero; 045 private final Map<FunCall, List<String>> scopeExprs = 046 new HashMap<FunCall, List<String>>(); 047 048 /** 049 * Creates a ValidatorImpl. 050 * 051 * @param funTable Function table 052 * 053 * @pre funTable != null 054 */ 055 protected ValidatorImpl(FunTable funTable) { 056 Util.assertPrecondition(funTable != null, "funTable != null"); 057 this.funTable = funTable; 058 } 059 060 public Exp validate(Exp exp, boolean scalar) { 061 Exp resolved; 062 try { 063 resolved = (Exp) resolvedNodes.get(exp); 064 } catch (ClassCastException e) { 065 // A classcast exception will occur if there is a String 066 // placeholder in the map. This is an internal error -- should 067 // not occur for any query, valid or invalid. 068 throw Util.newInternal( 069 e, 070 "Infinite recursion encountered while validating '" 071 + Util.unparse(exp) + "'"); 072 } 073 if (resolved == null) { 074 try { 075 stack.push((QueryPart) exp); 076 // To prevent recursion, put in a placeholder while we're 077 // resolving. 078 resolvedNodes.put((QueryPart) exp, placeHolder); 079 resolved = exp.accept(this); 080 Util.assertTrue(resolved != null); 081 resolvedNodes.put((QueryPart) exp, (QueryPart) resolved); 082 } finally { 083 stack.pop(); 084 } 085 } 086 087 if (scalar) { 088 final Type type = resolved.getType(); 089 if (!TypeUtil.canEvaluate(type)) { 090 String exprString = Util.unparse(resolved); 091 throw MondrianResource.instance().MdxMemberExpIsSet.ex( 092 exprString); 093 } 094 } 095 096 return resolved; 097 } 098 099 public void validate(ParameterExpr parameterExpr) { 100 ParameterExpr resolved = 101 (ParameterExpr) resolvedNodes.get(parameterExpr); 102 if (resolved != null) { 103 return; // already resolved 104 } 105 try { 106 stack.push(parameterExpr); 107 resolvedNodes.put(parameterExpr, placeHolder); 108 resolved = (ParameterExpr) parameterExpr.accept(this); 109 assert resolved != null; 110 resolvedNodes.put(parameterExpr, resolved); 111 } finally { 112 stack.pop(); 113 } 114 } 115 116 public void validate(MemberProperty memberProperty) { 117 MemberProperty resolved = 118 (MemberProperty) resolvedNodes.get(memberProperty); 119 if (resolved != null) { 120 return; // already resolved 121 } 122 try { 123 stack.push(memberProperty); 124 resolvedNodes.put(memberProperty, placeHolder); 125 memberProperty.resolve(this); 126 resolvedNodes.put(memberProperty, memberProperty); 127 } finally { 128 stack.pop(); 129 } 130 } 131 132 public void validate(QueryAxis axis) { 133 final QueryAxis resolved = (QueryAxis) resolvedNodes.get(axis); 134 if (resolved != null) { 135 return; // already resolved 136 } 137 try { 138 stack.push(axis); 139 resolvedNodes.put(axis, placeHolder); 140 axis.resolve(this); 141 resolvedNodes.put(axis, axis); 142 } finally { 143 stack.pop(); 144 } 145 } 146 147 public void validate(Formula formula) { 148 final Formula resolved = (Formula) resolvedNodes.get(formula); 149 if (resolved != null) { 150 return; // already resolved 151 } 152 try { 153 stack.push(formula); 154 resolvedNodes.put(formula, placeHolder); 155 formula.accept(this); 156 resolvedNodes.put(formula, formula); 157 } finally { 158 stack.pop(); 159 } 160 } 161 162 public FunDef getDef( 163 Exp[] args, 164 String funName, 165 Syntax syntax) 166 { 167 // Compute signature first. It makes debugging easier. 168 final String signature = 169 syntax.getSignature( 170 funName, Category.Unknown, ExpBase.getTypes(args)); 171 172 // Resolve function by its upper-case name first. If there is only one 173 // function with that name, stop immediately. If there is more than 174 // function, use some custom method, which generally involves looking 175 // at the type of one of its arguments. 176 List<Resolver> resolvers = funTable.getResolvers(funName, syntax); 177 assert resolvers != null; 178 179 final List<Resolver.Conversion> conversionList = 180 new ArrayList<Resolver.Conversion>(); 181 int minConversionCost = Integer.MAX_VALUE; 182 List<FunDef> matchDefs = new ArrayList<FunDef>(); 183 List<Resolver.Conversion> matchConversionList = null; 184 for (Resolver resolver : resolvers) { 185 conversionList.clear(); 186 FunDef def = resolver.resolve(args, this, conversionList); 187 if (def != null) { 188 int conversionCost = sumConversionCost(conversionList); 189 if (conversionCost < minConversionCost) { 190 minConversionCost = conversionCost; 191 matchDefs.clear(); 192 matchDefs.add(def); 193 matchConversionList = 194 new ArrayList<Resolver.Conversion>(conversionList); 195 } else if (conversionCost == minConversionCost) { 196 matchDefs.add(def); 197 } else { 198 // ignore this match -- it required more coercions than 199 // other overloadings we've seen 200 } 201 } 202 } 203 switch (matchDefs.size()) { 204 case 0: 205 throw MondrianResource.instance().NoFunctionMatchesSignature.ex( 206 signature); 207 case 1: 208 break; 209 default: 210 final StringBuilder buf = new StringBuilder(); 211 for (FunDef matchDef : matchDefs) { 212 if (buf.length() > 0) { 213 buf.append(", "); 214 } 215 buf.append(matchDef.getSignature()); 216 } 217 throw MondrianResource.instance() 218 .MoreThanOneFunctionMatchesSignature.ex( 219 signature, 220 buf.toString()); 221 } 222 223 final FunDef matchDef = matchDefs.get(0); 224 for (Resolver.Conversion conversion : matchConversionList) { 225 conversion.checkValid(); 226 conversion.apply(this, Arrays.asList(args)); 227 } 228 229 return matchDef; 230 } 231 232 public boolean alwaysResolveFunDef() { 233 return false; 234 } 235 236 private int sumConversionCost( 237 List<Resolver.Conversion> conversionList) 238 { 239 int cost = 0; 240 for (Resolver.Conversion conversion : conversionList) { 241 cost += conversion.getCost(); 242 } 243 return cost; 244 } 245 246 public boolean canConvert( 247 int ordinal, Exp fromExp, int to, List<Resolver.Conversion> conversions) 248 { 249 return TypeUtil.canConvert( 250 ordinal, 251 fromExp.getType(), 252 to, 253 conversions); 254 } 255 256 public boolean requiresExpression() { 257 return requiresExpression(stack.size() - 1); 258 } 259 260 private boolean requiresExpression(int n) { 261 if (n < 1) { 262 return false; 263 } 264 final Object parent = stack.get(n - 1); 265 if (parent instanceof Formula) { 266 return ((Formula) parent).isMember(); 267 } else if (parent instanceof ResolvedFunCall) { 268 final ResolvedFunCall funCall = (ResolvedFunCall) parent; 269 if (funCall.getFunDef().getSyntax() == Syntax.Parentheses) { 270 return requiresExpression(n - 1); 271 } else { 272 int k = whichArg(funCall, (Exp) stack.get(n)); 273 if (k < 0) { 274 // Arguments of call have mutated since call was placed 275 // on stack. Presumably the call has already been 276 // resolved correctly, so the answer we give here is 277 // irrelevant. 278 return false; 279 } 280 final FunDef funDef = funCall.getFunDef(); 281 final int[] parameterTypes = funDef.getParameterCategories(); 282 return parameterTypes[k] != Category.Set; 283 } 284 } else if (parent instanceof UnresolvedFunCall) { 285 final UnresolvedFunCall funCall = (UnresolvedFunCall) parent; 286 if (funCall.getSyntax() == Syntax.Parentheses 287 || funCall.getFunName().equals("*")) 288 { 289 return requiresExpression(n - 1); 290 } else { 291 int k = whichArg(funCall, (Exp) stack.get(n)); 292 if (k < 0) { 293 // Arguments of call have mutated since call was placed 294 // on stack. Presumably the call has already been 295 // resolved correctly, so the answer we give here is 296 // irrelevant. 297 return false; 298 } 299 return requiresExpression(funCall, k); 300 } 301 } else { 302 return false; 303 } 304 } 305 306 /** 307 * Returns whether the <code>k</code>th argument to a function call 308 * has to be an expression. 309 */ 310 boolean requiresExpression( 311 UnresolvedFunCall funCall, 312 int k) 313 { 314 // The function call has not been resolved yet. In fact, this method 315 // may have been invoked while resolving the child. Consider this: 316 // CrossJoin([Measures].[Unit Sales] * [Measures].[Store Sales]) 317 // 318 // In order to know whether to resolve '*' to the multiplication 319 // operator (which returns a scalar) or the crossjoin operator 320 // (which returns a set) we have to know what kind of expression is 321 // expected. 322 List<Resolver> resolvers = 323 funTable.getResolvers( 324 funCall.getFunName(), 325 funCall.getSyntax()); 326 for (Resolver resolver2 : resolvers) { 327 if (!resolver2.requiresExpression(k)) { 328 // This resolver accepts a set in this argument position, 329 // therefore we don't REQUIRE a scalar expression. 330 return false; 331 } 332 } 333 return true; 334 } 335 336 public FunTable getFunTable() { 337 return funTable; 338 } 339 340 public Parameter createOrLookupParam( 341 boolean definition, 342 String name, 343 Type type, 344 Exp defaultExp, 345 String description) 346 { 347 final SchemaReader schemaReader = getQuery().getSchemaReader(false); 348 Parameter param = schemaReader.getParameter(name); 349 350 if (definition) { 351 if (param != null) { 352 if (param.getScope() == Parameter.Scope.Statement) { 353 ParameterImpl paramImpl = (ParameterImpl) param; 354 paramImpl.setDescription(description); 355 paramImpl.setDefaultExp(defaultExp); 356 paramImpl.setType(type); 357 } 358 return param; 359 } 360 param = new ParameterImpl( 361 name, 362 defaultExp, description, type); 363 364 // Append it to the list of known parameters. 365 defineParameter(param); 366 return param; 367 } else { 368 if (param != null) { 369 return param; 370 } 371 throw MondrianResource.instance().UnknownParameter.ex(name); 372 } 373 } 374 375 private int whichArg(final FunCall node, final Exp arg) { 376 final Exp[] children = node.getArgs(); 377 for (int i = 0; i < children.length; i++) { 378 if (children[i] == arg) { 379 return i; 380 } 381 } 382 return -1; 383 } 384 385 /** 386 * Defines a parameter. 387 * 388 * @param param Parameter 389 */ 390 protected abstract void defineParameter(Parameter param); 391} 392 393// End ValidatorImpl.java 394