001 /* 002 // $Id: //open/mondrian/src/main/mondrian/olap/Query.java#114 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 1998-2002 Kana Software, Inc. 007 // Copyright (C) 2001-2008 Julian Hyde and others 008 // All Rights Reserved. 009 // You must accept the terms of that agreement to use this software. 010 // 011 // jhyde, 20 January, 1999 012 */ 013 014 package mondrian.olap; 015 import mondrian.calc.Calc; 016 import mondrian.calc.ExpCompiler; 017 import mondrian.calc.ResultStyle; 018 import mondrian.mdx.*; 019 import mondrian.olap.fun.ParameterFunDef; 020 import mondrian.olap.type.*; 021 import mondrian.resource.MondrianResource; 022 import mondrian.rolap.*; 023 024 import java.io.*; 025 import java.util.*; 026 027 /** 028 * <code>Query</code> is an MDX query. 029 * 030 * <p>It is created by calling {@link Connection#parseQuery}, 031 * and executed by calling {@link Connection#execute}, 032 * to return a {@link Result}.</p> 033 * 034 * <h3>Query control</h3> 035 * 036 * <p>Most queries are model citizens, executing quickly (often using cached 037 * results from previous queries), but some queries take more time, or more 038 * database resources, or more results, than is reasonable. Mondrian offers 039 * three ways to control rogue queries:<ul> 040 * 041 * <li>You can set a query timeout by setting the 042 * {@link MondrianProperties#QueryTimeout} parameter. If the query 043 * takes longer to execute than the value of this parameter, the system 044 * will kill it.</li> 045 * 046 * <li>The {@link MondrianProperties#QueryLimit} parameter limits the number 047 * of cells returned by a query.</li> 048 * 049 * <li>At any time while a query is executing, another thread can call the 050 * {@link #cancel()} method. The call to {@link Connection#execute(Query)} 051 * will throw an exception.</li> 052 * 053 * </ul> 054 * 055 * @author jhyde 056 * @version $Id: //open/mondrian/src/main/mondrian/olap/Query.java#114 $ 057 */ 058 public class Query extends QueryPart { 059 060 /** 061 * public-private: This must be public because it is still accessed in rolap.RolapCube 062 */ 063 public Formula[] formulas; 064 065 /** 066 * public-private: This must be public because it is still accessed in rolap.RolapConnection 067 */ 068 public QueryAxis[] axes; 069 070 /** 071 * public-private: This must be public because it is still accessed in rolap.RolapResult 072 */ 073 public QueryAxis slicerAxis; 074 075 /** 076 * Definitions of all parameters used in this query. 077 */ 078 private final List<Parameter> parameters = new ArrayList<Parameter>(); 079 080 private final Map<String, Parameter> parametersByName = 081 new HashMap<String, Parameter>(); 082 083 /** 084 * Cell properties. Not currently used. 085 */ 086 private final QueryPart[] cellProps; 087 088 /** 089 * Cube this query belongs to. 090 */ 091 private final Cube cube; 092 093 private final Connection connection; 094 public Calc[] axisCalcs; 095 public Calc slicerCalc; 096 097 /** 098 * Set of FunDefs for which alerts about non-native evaluation 099 * have already been posted. 100 */ 101 Set<FunDef> alertedNonNativeFunDefs; 102 103 /** 104 * Start time of query execution 105 */ 106 private long startTime; 107 108 /** 109 * Query timeout, in milliseconds 110 */ 111 private long queryTimeout; 112 113 /** 114 * If true, cancel this query 115 */ 116 private boolean isCanceled; 117 118 /** 119 * If not <code>null</code>, this query was notified that it 120 * might cause an OutOfMemoryError. 121 */ 122 private String outOfMemoryMsg; 123 124 /** 125 * If true, query is in the middle of execution 126 */ 127 private boolean isExecuting; 128 129 /** 130 * Unique list of members referenced from the measures dimension. 131 * Will be used to determine if cross joins can be processed natively 132 * for virtual cubes. 133 */ 134 private Set<Member> measuresMembers; 135 136 /** 137 * If true, virtual cubes can be processed using native cross joins. 138 * It defaults to true, unless functions are applied on measures. 139 */ 140 private boolean nativeCrossJoinVirtualCube; 141 142 /** 143 * Used for virtual cubes. 144 * Comtains a list of base cubes related to a virtual cube 145 */ 146 private List<RolapCube> baseCubes; 147 148 /** 149 * If true, loading schema 150 */ 151 private boolean load; 152 153 /** 154 * If true, enforce validation even when ignoreInvalidMembers is set. 155 */ 156 private boolean strictValidation; 157 158 /** 159 * How should the query be returned? Valid values are: 160 * ResultStyle.ITERABLE 161 * ResultStyle.LIST 162 * ResultStyle.MUTABLE_LIST 163 * For java4, use LIST 164 */ 165 private ResultStyle resultStyle = (Util.Retrowoven) 166 ? ResultStyle.LIST : ResultStyle.ITERABLE; 167 168 169 private Map<String, Object> evalCache = new HashMap<String, Object>(); 170 171 /** 172 * Creates a Query. 173 */ 174 public Query( 175 Connection connection, 176 Formula[] formulas, 177 QueryAxis[] axes, 178 String cube, 179 QueryAxis slicerAxis, 180 QueryPart[] cellProps, 181 boolean load, 182 boolean strictValidation) { 183 this( 184 connection, 185 Util.lookupCube(connection.getSchemaReader(), cube, true), 186 formulas, 187 axes, 188 slicerAxis, 189 cellProps, 190 new Parameter[0], 191 load, 192 strictValidation); 193 } 194 195 /** 196 * Creates a Query. 197 */ 198 public Query( 199 Connection connection, 200 Cube mdxCube, 201 Formula[] formulas, 202 QueryAxis[] axes, 203 QueryAxis slicerAxis, 204 QueryPart[] cellProps, 205 Parameter[] parameters, 206 boolean load, 207 boolean strictValidation) { 208 this.connection = connection; 209 this.cube = mdxCube; 210 this.formulas = formulas; 211 this.axes = axes; 212 normalizeAxes(); 213 this.slicerAxis = slicerAxis; 214 this.cellProps = cellProps; 215 this.parameters.addAll(Arrays.asList(parameters)); 216 this.isExecuting = false; 217 this.queryTimeout = 218 MondrianProperties.instance().QueryTimeout.get() * 1000; 219 this.measuresMembers = new HashSet<Member>(); 220 // assume, for now, that cross joins on virtual cubes can be 221 // processed natively; as we parse the query, we'll know otherwise 222 this.nativeCrossJoinVirtualCube = true; 223 this.load = load; 224 this.strictValidation = strictValidation; 225 this.alertedNonNativeFunDefs = new HashSet<FunDef>(); 226 resolve(); 227 } 228 229 /** 230 * Sets the timeout in milliseconds of this Query. 231 * 232 * <p>Zero means no timeout. 233 * 234 * @param queryTimeoutMillis Timeout in milliseconds 235 */ 236 public void setQueryTimeoutMillis(long queryTimeoutMillis) { 237 this.queryTimeout = queryTimeoutMillis; 238 } 239 240 /** 241 * Checks whether the property name is present in the query. 242 */ 243 public boolean hasCellProperty(String propertyName) { 244 for (QueryPart cellProp : cellProps) { 245 if (((CellProperty)cellProp).isNameEquals(propertyName)) { 246 return true; 247 } 248 } 249 return false; 250 } 251 252 /** 253 * Checks whether any cell property present in the query 254 */ 255 public boolean isCellPropertyEmpty() { 256 return cellProps.length == 0; 257 } 258 259 /** 260 * Adds a new formula specifying a set 261 * to an existing query. 262 */ 263 public void addFormula(Id id, Exp exp) { 264 addFormula(id, exp, new MemberProperty[0]); 265 } 266 267 /** 268 * Adds a new formula specifying a member 269 * to an existing query. 270 * 271 * @param id Name of member 272 * @param exp Expression for member 273 * @param memberProperties Properties of member 274 */ 275 public void addFormula( 276 Id id, 277 Exp exp, 278 MemberProperty[] memberProperties) 279 { 280 Formula newFormula = new Formula(id, exp, memberProperties); 281 int formulaCount = 0; 282 if (formulas.length > 0) { 283 formulaCount = formulas.length; 284 } 285 Formula[] newFormulas = new Formula[formulaCount + 1]; 286 System.arraycopy(formulas, 0, newFormulas, 0, formulaCount); 287 newFormulas[formulaCount] = newFormula; 288 formulas = newFormulas; 289 resolve(); 290 } 291 292 public Validator createValidator() { 293 return new StackValidator(connection.getSchema().getFunTable()); 294 } 295 296 public Validator createValidator(FunTable functionTable) { 297 StackValidator validator; 298 validator = new StackValidator(functionTable); 299 return validator; 300 } 301 302 public Object clone() { 303 return new Query( 304 connection, 305 cube, 306 Formula.cloneArray(formulas), 307 QueryAxis.cloneArray(axes), 308 (slicerAxis == null) ? null : (QueryAxis) slicerAxis.clone(), 309 cellProps, 310 parameters.toArray(new Parameter[parameters.size()]), 311 load, 312 strictValidation); 313 } 314 315 public Query safeClone() { 316 return (Query) clone(); 317 } 318 319 public Connection getConnection() { 320 return connection; 321 } 322 323 /** 324 * Issues a cancel request on this Query object. Once the thread 325 * running the query detects the cancel request, the query execution will 326 * throw an exception. See <code>BasicQueryTest.testCancel</code> for an 327 * example of usage of this method. 328 */ 329 public void cancel() { 330 isCanceled = true; 331 } 332 333 void setOutOfMemory(String msg) { 334 outOfMemoryMsg = msg; 335 } 336 337 /** 338 * Checks if either a cancel request has been issued on the query or 339 * the execution time has exceeded the timeout value (if one has been 340 * set). Exceptions are raised if either of these two conditions are 341 * met. This method should be called periodically during query execution 342 * to ensure timely detection of these events, particularly before/after 343 * any potentially long running operations. 344 */ 345 public void checkCancelOrTimeout() { 346 if (!isExecuting) { 347 return; 348 } 349 if (isCanceled) { 350 throw MondrianResource.instance().QueryCanceled.ex(); 351 } 352 if (queryTimeout > 0) { 353 long currTime = System.currentTimeMillis(); 354 if ((currTime - startTime) >= queryTimeout) { 355 throw MondrianResource.instance().QueryTimeout.ex( 356 queryTimeout / 1000); 357 } 358 } 359 if (outOfMemoryMsg != null) { 360 throw new MemoryLimitExceededException(outOfMemoryMsg); 361 } 362 } 363 364 /** 365 * Sets the start time of query execution. Used to detect timeout for 366 * queries. 367 */ 368 public void setQueryStartTime() { 369 startTime = System.currentTimeMillis(); 370 isExecuting = true; 371 } 372 373 /** 374 * Gets the query start time 375 * @return start time 376 */ 377 public long getQueryStartTime() { 378 return startTime; 379 } 380 381 /** 382 * Called when query execution has completed. Once query execution has 383 * ended, it is not possible to cancel or timeout the query until it 384 * starts executing again. 385 */ 386 public void setQueryEndExecution() { 387 isExecuting = false; 388 } 389 390 /** 391 * Determines whether an alert for non-native evaluation needs 392 * to be posted. 393 * 394 * @param funDef function type to alert for 395 * 396 * @return true if alert should be raised 397 */ 398 public boolean shouldAlertForNonNative(FunDef funDef) { 399 return alertedNonNativeFunDefs.add(funDef); 400 } 401 402 private void normalizeAxes() { 403 for (int i = 0; i < axes.length; i++) { 404 AxisOrdinal correctOrdinal = AxisOrdinal.forLogicalOrdinal(i); 405 if (axes[i].getAxisOrdinal() != correctOrdinal) { 406 for (int j = i + 1; j < axes.length; j++) { 407 if (axes[j].getAxisOrdinal() == correctOrdinal) { 408 // swap axes 409 QueryAxis temp = axes[i]; 410 axes[i] = axes[j]; 411 axes[j] = temp; 412 break; 413 } 414 } 415 } 416 } 417 } 418 419 /** 420 * Performs type-checking and validates internal consistency of a query, 421 * using the default resolver. 422 * 423 * <p>This method is called automatically when a query is created; you need 424 * to call this method manually if you have modified the query's expression 425 * tree in any way. 426 */ 427 public void resolve() { 428 final Validator validator = createValidator(); 429 resolve(validator); // resolve self and children 430 // Create a dummy result so we can use its evaluator 431 final Evaluator evaluator = RolapUtil.createEvaluator(this); 432 ExpCompiler compiler = createCompiler(evaluator, validator, Collections.singletonList(resultStyle)); 433 compile(compiler); 434 } 435 436 /** 437 * @return true if the relevant property for ignoring invalid members is 438 * set to true for this query's environment (a different property is 439 * checked depending on whether environment is schema load vs query 440 * validation) 441 */ 442 public boolean ignoreInvalidMembers() 443 { 444 MondrianProperties props = MondrianProperties.instance(); 445 return 446 !strictValidation && 447 ((load && props.IgnoreInvalidMembers.get()) || (!load && props.IgnoreInvalidMembersDuringQuery.get())); 448 } 449 450 /** 451 * A Query's ResultStyle can only be one of the following: 452 * ResultStyle.ITERABLE 453 * ResultStyle.LIST 454 * ResultStyle.MUTABLE_LIST 455 * 456 * @param resultStyle 457 */ 458 public void setResultStyle(ResultStyle resultStyle) { 459 switch (resultStyle) { 460 case ITERABLE: 461 // For java4, use LIST 462 this.resultStyle = (Util.Retrowoven) 463 ? ResultStyle.LIST : ResultStyle.ITERABLE; 464 break; 465 case LIST: 466 case MUTABLE_LIST: 467 this.resultStyle = resultStyle; 468 break; 469 default: 470 throw ResultStyleException.generateBadType( 471 ResultStyle.ITERABLE_LIST_MUTABLELIST, 472 resultStyle); 473 } 474 } 475 476 public ResultStyle getResultStyle() { 477 return resultStyle; 478 } 479 480 /** 481 * Generates compiled forms of all expressions. 482 * 483 * @param compiler Compiler 484 */ 485 private void compile(ExpCompiler compiler) { 486 if (formulas != null) { 487 for (Formula formula : formulas) { 488 formula.compile(); 489 } 490 } 491 492 if (axes != null) { 493 axisCalcs = new Calc[axes.length]; 494 for (int i = 0; i < axes.length; i++) { 495 axisCalcs[i] = axes[i].compile( 496 compiler, 497 Collections.singletonList(resultStyle)); 498 } 499 } 500 if (slicerAxis != null) { 501 slicerCalc = slicerAxis.compile( 502 compiler, 503 Collections.singletonList(resultStyle)); 504 } 505 } 506 507 /** 508 * Performs type-checking and validates internal consistency of a query. 509 * 510 * @param validator Validator 511 */ 512 public void resolve(Validator validator) { 513 // Before commencing validation, create all calculated members, 514 // calculated sets, and parameters. 515 if (formulas != null) { 516 // Resolving of formulas should be done in two parts 517 // because formulas might depend on each other, so all calculated 518 // mdx elements have to be defined during resolve. 519 for (Formula formula : formulas) { 520 formula.createElement(validator.getQuery()); 521 } 522 } 523 524 // Register all parameters. 525 parameters.clear(); 526 parametersByName.clear(); 527 accept( 528 new MdxVisitorImpl() { 529 public Object visit(ParameterExpr parameterExpr) { 530 Parameter parameter = parameterExpr.getParameter(); 531 if (!parameters.contains(parameter)) { 532 parameters.add(parameter); 533 parametersByName.put(parameter.getName(), parameter); 534 } 535 return null; 536 } 537 538 public Object visit(UnresolvedFunCall call) { 539 if (call.getFunName().equals("Parameter")) { 540 // Is there already a parameter with this name? 541 String parameterName = 542 ParameterFunDef.getParameterName(call.getArgs()); 543 if (parametersByName.get(parameterName) != null) { 544 throw MondrianResource.instance(). 545 ParameterDefinedMoreThanOnce.ex(parameterName); 546 } 547 548 Type type = 549 ParameterFunDef.getParameterType(call.getArgs()); 550 551 // Create a temporary parameter. We don't know its 552 // type yet. The default of NULL is temporary. 553 Parameter parameter = new ParameterImpl( 554 parameterName, Literal.nullValue, null, type); 555 parameters.add(parameter); 556 parametersByName.put(parameterName, parameter); 557 } 558 return null; 559 } 560 } 561 ); 562 563 // Validate formulas. 564 if (formulas != null) { 565 for (Formula formula : formulas) { 566 validator.validate(formula); 567 } 568 } 569 570 // Validate axes. 571 if (axes != null) { 572 Set<String> axisNames = new HashSet<String>(); 573 for (QueryAxis axis : axes) { 574 validator.validate(axis); 575 if (!axisNames.add(axis.getAxisName())) { 576 throw MondrianResource.instance().DuplicateAxis.ex( 577 axis.getAxisName()); 578 } 579 } 580 } 581 if (slicerAxis != null) { 582 slicerAxis.validate(validator); 583 } 584 585 // Make sure that no dimension is used on more than one axis. 586 final Dimension[] dimensions = getCube().getDimensions(); 587 for (Dimension dimension : dimensions) { 588 int useCount = 0; 589 for (int j = -1; j < axes.length; j++) { 590 final QueryAxis axisExp; 591 if (j < 0) { 592 if (slicerAxis == null) { 593 continue; 594 } 595 axisExp = slicerAxis; 596 } else { 597 axisExp = axes[j]; 598 } 599 if (axisExp.getSet().getType().usesDimension(dimension, true)) { 600 ++useCount; 601 } 602 } 603 if (useCount > 1) { 604 throw MondrianResource.instance().DimensionInIndependentAxes.ex( 605 dimension.getUniqueName()); 606 } 607 } 608 } 609 610 public void unparse(PrintWriter pw) { 611 if (formulas != null) { 612 for (int i = 0; i < formulas.length; i++) { 613 if (i == 0) { 614 pw.print("with "); 615 } else { 616 pw.print(" "); 617 } 618 formulas[i].unparse(pw); 619 pw.println(); 620 } 621 } 622 pw.print("select "); 623 if (axes != null) { 624 for (int i = 0; i < axes.length; i++) { 625 axes[i].unparse(pw); 626 if (i < axes.length - 1) { 627 pw.println(","); 628 pw.print(" "); 629 } else { 630 pw.println(); 631 } 632 } 633 } 634 if (cube != null) { 635 pw.println("from [" + cube.getName() + "]"); 636 } 637 if (slicerAxis != null) { 638 pw.print("where "); 639 slicerAxis.unparse(pw); 640 pw.println(); 641 } 642 } 643 644 /** Returns the MDX query string. */ 645 public String toString() { 646 resolve(); 647 return Util.unparse(this); 648 } 649 650 public Object[] getChildren() { 651 // Chidren are axes, slicer, and formulas (in that order, to be 652 // consistent with replaceChild). 653 List<QueryPart> list = new ArrayList<QueryPart>(); 654 list.addAll(Arrays.asList(axes)); 655 if (slicerAxis != null) { 656 list.add(slicerAxis); 657 } 658 list.addAll(Arrays.asList(formulas)); 659 return list.toArray(); 660 } 661 662 public QueryAxis getSlicerAxis() { 663 return slicerAxis; 664 } 665 666 public void setSlicerAxis(QueryAxis axis) { 667 this.slicerAxis = axis; 668 } 669 670 /** 671 * Adds a level to an axis expression. 672 */ 673 public void addLevelToAxis(AxisOrdinal axis, Level level) { 674 assert axis != null; 675 axes[axis.logicalOrdinal()].addLevel(level); 676 } 677 678 /** 679 * Returns the hierarchies in an expression. 680 * 681 * <p>If the expression's type is a dimension with several hierarchies, 682 * assumes that the expression yields a member of the first (default) 683 * hierarchy of the dimension. 684 * 685 * <p>For example, the expression 686 * <blockquote><code>Crossjoin( 687 * Hierarchize( 688 * Union( 689 * {[Time].LastSibling}, [Time].LastSibling.Children)), 690 * {[Measures].[Unit Sales], [Measures].[Store Cost]})</code> 691 * </blockquote> 692 * 693 * has type <code>{[Time.Monthly], [Measures]}</code> even though 694 * <code>[Time].LastSibling</code> might return a member of either 695 * [Time.Monthly] or [Time.Weekly]. 696 */ 697 private Hierarchy[] collectHierarchies(Exp queryPart) { 698 Type exprType = queryPart.getType(); 699 if (exprType instanceof SetType) { 700 exprType = ((SetType) exprType).getElementType(); 701 } 702 if (exprType instanceof TupleType) { 703 final Type[] types = ((TupleType) exprType).elementTypes; 704 ArrayList<Hierarchy> hierarchyList = new ArrayList<Hierarchy>(); 705 for (Type type : types) { 706 hierarchyList.add(getTypeHierarchy(type)); 707 } 708 return hierarchyList.toArray(new Hierarchy[hierarchyList.size()]); 709 } 710 return new Hierarchy[] {getTypeHierarchy(exprType)}; 711 } 712 713 private Hierarchy getTypeHierarchy(final Type type) { 714 Hierarchy hierarchy = type.getHierarchy(); 715 if (hierarchy != null) { 716 return hierarchy; 717 } 718 final Dimension dimension = type.getDimension(); 719 if (dimension != null) { 720 return dimension.getHierarchy(); 721 } 722 return null; 723 } 724 725 /** 726 * Assigns a value to the parameter with a given name. 727 * 728 * @throws RuntimeException if there is not parameter with the given name 729 */ 730 public void setParameter(String parameterName, String value) { 731 // Need to resolve query before we set parameters, in order to create 732 // slots to store them in. (This code will go away when parameters 733 // belong to prepared statements.) 734 if (parameters.isEmpty()) { 735 resolve(); 736 } 737 738 Parameter param = getSchemaReader(false).getParameter(parameterName); 739 if (param == null) { 740 throw MondrianResource.instance().UnknownParameter.ex(parameterName); 741 } 742 if (!param.isModifiable()) { 743 throw MondrianResource.instance().ParameterIsNotModifiable.ex( 744 parameterName, param.getScope().name()); 745 } 746 final Exp exp = quickParse( 747 TypeUtil.typeToCategory(param.getType()), value, this); 748 param.setValue(exp); 749 } 750 751 private static Exp quickParse(int category, String value, Query query) { 752 switch (category) { 753 case Category.Numeric: 754 return Literal.create(new Double(value)); 755 case Category.String: 756 return Literal.createString(value); 757 case Category.Member: 758 Member member = 759 (Member) Util.lookup(query, Util.parseIdentifier(value)); 760 return new MemberExpr(member); 761 default: 762 throw Category.instance.badValue(category); 763 } 764 } 765 766 /** 767 * Swaps the x- and y- axes. 768 * Does nothing if the number of axes != 2. 769 */ 770 public void swapAxes() { 771 if (axes.length == 2) { 772 Exp e0 = axes[0].getSet(); 773 boolean nonEmpty0 = axes[0].isNonEmpty(); 774 Exp e1 = axes[1].getSet(); 775 boolean nonEmpty1 = axes[1].isNonEmpty(); 776 axes[1].setSet(e0); 777 axes[1].setNonEmpty(nonEmpty0); 778 axes[0].setSet(e1); 779 axes[0].setNonEmpty(nonEmpty1); 780 // showSubtotals ??? 781 } 782 } 783 784 /** 785 * Returns the parameters defined in this query. 786 */ 787 public Parameter[] getParameters() { 788 return parameters.toArray(new Parameter[parameters.size()]); 789 } 790 791 public Cube getCube() { 792 return cube; 793 } 794 795 /** 796 * Returns a schema reader. 797 * 798 * @param accessControlled If true, schema reader returns only elements 799 * which are accessible to the connection's current role 800 * 801 * @return schema reader 802 */ 803 public SchemaReader getSchemaReader(boolean accessControlled) { 804 final Role role; 805 if (accessControlled) { 806 // full access control 807 role = getConnection().getRole(); 808 } else { 809 role = null; 810 } 811 final SchemaReader cubeSchemaReader = cube.getSchemaReader(role); 812 return new QuerySchemaReader(cubeSchemaReader); 813 } 814 815 /** 816 * Looks up a member whose unique name is <code>memberUniqueName</code> 817 * from cache. If the member is not in cache, returns null. 818 */ 819 public Member lookupMemberFromCache(String memberUniqueName) { 820 // first look in defined members 821 for (Member member : getDefinedMembers()) { 822 if (Util.equalName(member.getUniqueName(), memberUniqueName) || 823 Util.equalName( 824 getUniqueNameWithoutAll(member), 825 memberUniqueName)) { 826 return member; 827 } 828 } 829 return null; 830 } 831 832 private String getUniqueNameWithoutAll(Member member) { 833 // build unique string 834 Member parentMember = member.getParentMember(); 835 if ((parentMember != null) && !parentMember.isAll()) { 836 return Util.makeFqName( 837 getUniqueNameWithoutAll(parentMember), 838 member.getName()); 839 } else { 840 return Util.makeFqName(member.getHierarchy(), member.getName()); 841 } 842 } 843 844 /** 845 * Looks up a named set. 846 */ 847 private NamedSet lookupNamedSet(String name) { 848 for (Formula formula : formulas) { 849 if (!formula.isMember() && 850 formula.getElement() != null && 851 formula.getName().equals(name)) { 852 return (NamedSet) formula.getElement(); 853 } 854 } 855 return null; 856 } 857 858 /** 859 * Returns an array of the formulas used in this query. 860 */ 861 public Formula[] getFormulas() { 862 return formulas; 863 } 864 865 /** 866 * Returns an array of this query's axes. 867 */ 868 public QueryAxis[] getAxes() { 869 return axes; 870 } 871 872 /** 873 * Remove a formula from the query. If <code>failIfUsedInQuery</code> is 874 * true, checks and throws an error if formula is used somewhere in the 875 * query. 876 */ 877 public void removeFormula(String uniqueName, boolean failIfUsedInQuery) { 878 Formula formula = findFormula(uniqueName); 879 if (failIfUsedInQuery && formula != null) { 880 OlapElement mdxElement = formula.getElement(); 881 //search the query tree to see if this formula expression is used 882 //anywhere (on the axes or in another formula) 883 Walker walker = new Walker(this); 884 while (walker.hasMoreElements()) { 885 Object queryElement = walker.nextElement(); 886 if (!queryElement.equals(mdxElement)) { 887 continue; 888 } 889 // mdxElement is used in the query. lets find on on which axis 890 // or formula 891 String formulaType = formula.isMember() 892 ? MondrianResource.instance().CalculatedMember.str() 893 : MondrianResource.instance().CalculatedSet.str(); 894 895 int i = 0; 896 Object parent = walker.getAncestor(i); 897 Object grandParent = walker.getAncestor(i + 1); 898 while ((parent != null) && (grandParent != null)) { 899 if (grandParent instanceof Query) { 900 if (parent instanceof Axis) { 901 throw MondrianResource.instance(). 902 MdxCalculatedFormulaUsedOnAxis.ex( 903 formulaType, 904 uniqueName, 905 ((QueryAxis) parent).getAxisName()); 906 907 } else if (parent instanceof Formula) { 908 String parentFormulaType = 909 ((Formula) parent).isMember() 910 ? MondrianResource.instance().CalculatedMember.str() 911 : MondrianResource.instance().CalculatedSet.str(); 912 throw MondrianResource.instance(). 913 MdxCalculatedFormulaUsedInFormula.ex( 914 formulaType, uniqueName, parentFormulaType, 915 ((Formula) parent).getUniqueName()); 916 917 } else { 918 throw MondrianResource.instance(). 919 MdxCalculatedFormulaUsedOnSlicer.ex( 920 formulaType, uniqueName); 921 } 922 } 923 ++i; 924 parent = walker.getAncestor(i); 925 grandParent = walker.getAncestor(i + 1); 926 } 927 throw MondrianResource.instance(). 928 MdxCalculatedFormulaUsedInQuery.ex( 929 formulaType, uniqueName, Util.unparse(this)); 930 } 931 } 932 933 // remove formula from query 934 List<Formula> formulaList = new ArrayList<Formula>(); 935 for (Formula formula1 : formulas) { 936 if (!formula1.getUniqueName().equalsIgnoreCase(uniqueName)) { 937 formulaList.add(formula1); 938 } 939 } 940 941 // it has been found and removed 942 this.formulas = formulaList.toArray(new Formula[0]); 943 } 944 945 /** 946 * Returns whether a formula can safely be removed from the query. It can be 947 * removed if the member or set it defines it not used anywhere else in the 948 * query, including in another formula. 949 * 950 * @param uniqueName Unique name of the member or set defined by the formula 951 * @return whether the formula can safely be removed 952 */ 953 public boolean canRemoveFormula(String uniqueName) { 954 Formula formula = findFormula(uniqueName); 955 if (formula == null) { 956 return false; 957 } 958 959 OlapElement mdxElement = formula.getElement(); 960 // Search the query tree to see if this formula expression is used 961 // anywhere (on the axes or in another formula). 962 Walker walker = new Walker(this); 963 while (walker.hasMoreElements()) { 964 Object queryElement = walker.nextElement(); 965 if (queryElement instanceof MemberExpr && 966 ((MemberExpr) queryElement).getMember().equals(mdxElement)) { 967 return false; 968 } 969 if (queryElement instanceof NamedSetExpr && 970 ((NamedSetExpr) queryElement).getNamedSet().equals(mdxElement)) { 971 return false; 972 } 973 } 974 return true; 975 } 976 977 /** 978 * Looks up a calculated member or set defined in this Query. 979 * 980 * @param uniqueName Unique name of calculated member or set 981 * @return formula defining calculated member, or null if not found 982 */ 983 public Formula findFormula(String uniqueName) { 984 for (Formula formula : formulas) { 985 if (formula.getUniqueName().equalsIgnoreCase(uniqueName)) { 986 return formula; 987 } 988 } 989 return null; 990 } 991 992 /** 993 * Finds formula by name and renames it to new name. 994 */ 995 public void renameFormula(String uniqueName, String newName) { 996 Formula formula = findFormula(uniqueName); 997 if (formula == null) { 998 throw MondrianResource.instance().MdxFormulaNotFound.ex( 999 "formula", uniqueName, Util.unparse(this)); 1000 } 1001 formula.rename(newName); 1002 } 1003 1004 List<Member> getDefinedMembers() { 1005 List<Member> definedMembers = new ArrayList<Member>(); 1006 for (final Formula formula : formulas) { 1007 if (formula.isMember() && 1008 formula.getElement() != null && 1009 getConnection().getRole().canAccess(formula.getElement())) { 1010 definedMembers.add((Member) formula.getElement()); 1011 } 1012 } 1013 return definedMembers; 1014 } 1015 1016 /** 1017 * Finds axis by index and sets flag to show empty cells on that axis. 1018 */ 1019 public void setAxisShowEmptyCells(int axis, boolean showEmpty) { 1020 if (axis >= axes.length) { 1021 throw MondrianResource.instance().MdxAxisShowSubtotalsNotSupported. 1022 ex(axis); 1023 } 1024 axes[axis].setNonEmpty(!showEmpty); 1025 } 1026 1027 /** 1028 * Returns <code>Hierarchy[]</code> used on <code>axis</code>. It calls 1029 * {@link #collectHierarchies}. 1030 */ 1031 public Hierarchy[] getMdxHierarchiesOnAxis(AxisOrdinal axis) { 1032 if (axis.logicalOrdinal() >= axes.length) { 1033 throw MondrianResource.instance().MdxAxisShowSubtotalsNotSupported. 1034 ex(axis.logicalOrdinal()); 1035 } 1036 QueryAxis queryAxis = (axis == AxisOrdinal.SLICER) ? 1037 slicerAxis : 1038 axes[axis.logicalOrdinal()]; 1039 return collectHierarchies(queryAxis.getSet()); 1040 } 1041 1042 /** 1043 * Compiles an expression, using a cached compiled expression if available. 1044 * 1045 * @param exp Expression 1046 * @param scalar Whether expression is scalar 1047 * @param resultStyle Preferred result style; if null, use query's default 1048 * result style; ignored if expression is scalar 1049 * @return compiled expression 1050 */ 1051 public Calc compileExpression( 1052 Exp exp, 1053 boolean scalar, 1054 ResultStyle resultStyle) 1055 { 1056 Evaluator evaluator = RolapEvaluator.create(this); 1057 final Validator validator = createValidator(); 1058 List<ResultStyle> resultStyleList; 1059 resultStyleList = 1060 Collections.singletonList( 1061 resultStyle != null ? resultStyle : this.resultStyle); 1062 final ExpCompiler compiler = 1063 createCompiler( 1064 evaluator, validator, resultStyleList); 1065 if (scalar) { 1066 return compiler.compileScalar(exp, false); 1067 } else { 1068 return compiler.compile(exp); 1069 } 1070 } 1071 1072 public ExpCompiler createCompiler() { 1073 Evaluator evaluator = RolapEvaluator.create(this); 1074 Validator validator = createValidator(); 1075 return createCompiler( 1076 evaluator, 1077 validator, 1078 Collections.singletonList(resultStyle)); 1079 } 1080 1081 private ExpCompiler createCompiler( 1082 final Evaluator evaluator, 1083 final Validator validator, 1084 List<ResultStyle> resultStyleList) 1085 { 1086 ExpCompiler compiler = 1087 ExpCompiler.Factory.getExpCompiler( 1088 evaluator, 1089 validator, 1090 resultStyleList); 1091 1092 final int expDeps = 1093 MondrianProperties.instance().TestExpDependencies.get(); 1094 if (expDeps > 0) { 1095 compiler = RolapUtil.createDependencyTestingCompiler(compiler); 1096 } 1097 return compiler; 1098 } 1099 1100 /** 1101 * Keeps track of references to members of the measures dimension 1102 * 1103 * @param olapElement potential measure member 1104 */ 1105 public void addMeasuresMembers(OlapElement olapElement) 1106 { 1107 if (olapElement instanceof Member) { 1108 Member member = (Member) olapElement; 1109 if (member.getDimension().getOrdinal(getCube()) == 0) { 1110 measuresMembers.add(member); 1111 } 1112 } 1113 } 1114 1115 /** 1116 * @return set of members from the measures dimension referenced within 1117 * this query 1118 */ 1119 public Set<Member> getMeasuresMembers() { 1120 return Collections.unmodifiableSet(measuresMembers); 1121 } 1122 1123 /** 1124 * Indicates that the query cannot use native cross joins to process 1125 * this virtual cube 1126 */ 1127 public void setVirtualCubeNonNativeCrossJoin() { 1128 nativeCrossJoinVirtualCube = false; 1129 } 1130 1131 /** 1132 * @return true if the query can use native cross joins on a virtual 1133 * cube 1134 */ 1135 public boolean nativeCrossJoinVirtualCube() { 1136 return nativeCrossJoinVirtualCube; 1137 } 1138 1139 /** 1140 * Saves away the base cubes related to the virtual cube 1141 * referenced in this query 1142 * 1143 * @param baseCubes set of base cubes 1144 */ 1145 public void setBaseCubes(List<RolapCube> baseCubes) { 1146 this.baseCubes = baseCubes; 1147 } 1148 1149 /** 1150 * return the set of base cubes associated with the virtual cube referenced 1151 * in this query 1152 * 1153 * @return set of base cubes 1154 */ 1155 public List<RolapCube> getBaseCubes() { 1156 return baseCubes; 1157 } 1158 1159 public Object accept(MdxVisitor visitor) { 1160 Object o = visitor.visit(this); 1161 1162 // visit formulas 1163 for (Formula formula : formulas) { 1164 formula.accept(visitor); 1165 } 1166 // visit axes 1167 for (QueryAxis axis : axes) { 1168 axis.accept(visitor); 1169 } 1170 if (slicerAxis != null) { 1171 slicerAxis.accept(visitor); 1172 } 1173 1174 return o; 1175 } 1176 1177 /** 1178 * Put an Object value into the evaluation cache with given key. 1179 * This is used by Calc's to store information between iterations 1180 * (rather than re-generate each time). 1181 * 1182 * @param key the cache key 1183 * @param value the cache value 1184 */ 1185 public void putEvalCache(String key, Object value) { 1186 evalCache.put(key, value); 1187 } 1188 1189 /** 1190 * Gets the Object associated with the value. 1191 * 1192 * @param key the cache key 1193 * @return the cached value or null. 1194 */ 1195 public Object getEvalCache(String key) { 1196 return evalCache.get(key); 1197 } 1198 1199 /** 1200 * Remove all entries in the evaluation cache 1201 */ 1202 public void clearEvalCache() { 1203 evalCache.clear(); 1204 } 1205 1206 /** 1207 * Default implementation of {@link Validator}. 1208 * 1209 * <p>Uses a stack to help us guess the type of our parent expression 1210 * before we've completely resolved our children -- necessary, 1211 * unfortunately, when figuring out whether the "*" operator denotes 1212 * multiplication or crossjoin. 1213 * 1214 * <p>Keeps track of which nodes have already been resolved, so we don't 1215 * try to resolve nodes which have already been resolved. (That would not 1216 * be wrong, but can cause resolution to be an <code>O(2^N)</code> 1217 * operation.) 1218 */ 1219 private class StackValidator implements Validator { 1220 private final Stack<QueryPart> stack = new Stack<QueryPart>(); 1221 private final FunTable funTable; 1222 private final Map<QueryPart, QueryPart> resolvedNodes = 1223 new HashMap<QueryPart, QueryPart>(); 1224 private final QueryPart placeHolder = Literal.zero; 1225 1226 /** 1227 * Creates a StackValidator. 1228 * 1229 * @pre funTable != null 1230 */ 1231 public StackValidator(FunTable funTable) { 1232 Util.assertPrecondition(funTable != null, "funTable != null"); 1233 this.funTable = funTable; 1234 } 1235 1236 public Query getQuery() { 1237 return Query.this; 1238 } 1239 1240 public Exp validate(Exp exp, boolean scalar) { 1241 Exp resolved; 1242 try { 1243 resolved = (Exp) resolvedNodes.get(exp); 1244 } catch (ClassCastException e) { 1245 // A classcast exception will occur if there is a String 1246 // placeholder in the map. This is an internal error -- should 1247 // not occur for any query, valid or invalid. 1248 throw Util.newInternal( 1249 e, 1250 "Infinite recursion encountered while validating '" + 1251 Util.unparse(exp) + "'"); 1252 } 1253 if (resolved == null) { 1254 try { 1255 stack.push((QueryPart) exp); 1256 // To prevent recursion, put in a placeholder while we're 1257 // resolving. 1258 resolvedNodes.put((QueryPart) exp, placeHolder); 1259 resolved = exp.accept(this); 1260 Util.assertTrue(resolved != null); 1261 resolvedNodes.put((QueryPart) exp, (QueryPart) resolved); 1262 } finally { 1263 stack.pop(); 1264 } 1265 } 1266 1267 if (scalar) { 1268 final Type type = resolved.getType(); 1269 if (!TypeUtil.canEvaluate(type)) { 1270 String exprString = Util.unparse(resolved); 1271 throw MondrianResource.instance().MdxMemberExpIsSet.ex(exprString); 1272 } 1273 } 1274 1275 return resolved; 1276 } 1277 1278 public void validate(ParameterExpr parameterExpr) { 1279 ParameterExpr resolved = 1280 (ParameterExpr) resolvedNodes.get(parameterExpr); 1281 if (resolved != null) { 1282 return; // already resolved 1283 } 1284 try { 1285 stack.push(parameterExpr); 1286 resolvedNodes.put(parameterExpr, placeHolder); 1287 resolved = (ParameterExpr) parameterExpr.accept(this); 1288 assert resolved != null; 1289 resolvedNodes.put(parameterExpr, resolved); 1290 } finally { 1291 stack.pop(); 1292 } 1293 } 1294 1295 public void validate(MemberProperty memberProperty) { 1296 MemberProperty resolved = 1297 (MemberProperty) resolvedNodes.get(memberProperty); 1298 if (resolved != null) { 1299 return; // already resolved 1300 } 1301 try { 1302 stack.push(memberProperty); 1303 resolvedNodes.put(memberProperty, placeHolder); 1304 memberProperty.resolve(this); 1305 resolvedNodes.put(memberProperty, memberProperty); 1306 } finally { 1307 stack.pop(); 1308 } 1309 } 1310 1311 public void validate(QueryAxis axis) { 1312 final QueryAxis resolved = (QueryAxis) resolvedNodes.get(axis); 1313 if (resolved != null) { 1314 return; // already resolved 1315 } 1316 try { 1317 stack.push(axis); 1318 resolvedNodes.put(axis, placeHolder); 1319 axis.resolve(this); 1320 resolvedNodes.put(axis, axis); 1321 } finally { 1322 stack.pop(); 1323 } 1324 } 1325 1326 public void validate(Formula formula) { 1327 final Formula resolved = (Formula) resolvedNodes.get(formula); 1328 if (resolved != null) { 1329 return; // already resolved 1330 } 1331 try { 1332 stack.push(formula); 1333 resolvedNodes.put(formula, placeHolder); 1334 formula.accept(this); 1335 resolvedNodes.put(formula, formula); 1336 } finally { 1337 stack.pop(); 1338 } 1339 } 1340 1341 public boolean canConvert(Exp fromExp, int to, int[] conversionCount) { 1342 return TypeUtil.canConvert( 1343 fromExp.getCategory(), 1344 to, 1345 conversionCount); 1346 } 1347 1348 public boolean requiresExpression() { 1349 return requiresExpression(stack.size() - 1); 1350 } 1351 1352 private boolean requiresExpression(int n) { 1353 if (n < 1) { 1354 return false; 1355 } 1356 final Object parent = stack.get(n - 1); 1357 if (parent instanceof Formula) { 1358 return ((Formula) parent).isMember(); 1359 } else if (parent instanceof ResolvedFunCall) { 1360 final ResolvedFunCall funCall = (ResolvedFunCall) parent; 1361 if (funCall.getFunDef().getSyntax() == Syntax.Parentheses) { 1362 return requiresExpression(n - 1); 1363 } else { 1364 int k = whichArg(funCall, (Exp) stack.get(n)); 1365 if (k < 0) { 1366 // Arguments of call have mutated since call was placed 1367 // on stack. Presumably the call has already been 1368 // resolved correctly, so the answer we give here is 1369 // irrelevant. 1370 return false; 1371 } 1372 final FunDef funDef = funCall.getFunDef(); 1373 final int[] parameterTypes = funDef.getParameterCategories(); 1374 return parameterTypes[k] != Category.Set; 1375 } 1376 } else if (parent instanceof UnresolvedFunCall) { 1377 final UnresolvedFunCall funCall = (UnresolvedFunCall) parent; 1378 if (funCall.getSyntax() == Syntax.Parentheses || 1379 funCall.getFunName() == "*") { 1380 return requiresExpression(n - 1); 1381 } else { 1382 int k = whichArg(funCall, (Exp) stack.get(n)); 1383 if (k < 0) { 1384 // Arguments of call have mutated since call was placed 1385 // on stack. Presumably the call has already been 1386 // resolved correctly, so the answer we give here is 1387 // irrelevant. 1388 return false; 1389 } 1390 return funTable.requiresExpression(funCall, k, this); 1391 } 1392 } else { 1393 return false; 1394 } 1395 } 1396 1397 public FunTable getFunTable() { 1398 return funTable; 1399 } 1400 1401 public Parameter createOrLookupParam( 1402 boolean definition, 1403 String name, 1404 Type type, 1405 Exp defaultExp, 1406 String description) 1407 { 1408 final SchemaReader schemaReader = getSchemaReader(false); 1409 Parameter param = schemaReader.getParameter(name); 1410 1411 if (definition) { 1412 if (param != null) { 1413 if (param.getScope() == Parameter.Scope.Statement) { 1414 ParameterImpl paramImpl = (ParameterImpl) param; 1415 paramImpl.setDescription(description); 1416 paramImpl.setDefaultExp(defaultExp); 1417 paramImpl.setType(type); 1418 } 1419 return param; 1420 } 1421 param = new ParameterImpl( 1422 name, 1423 defaultExp, description, type); 1424 1425 // Append it to the list of known parameters. 1426 parameters.add(param); 1427 parametersByName.put(name, param); 1428 return param; 1429 } else { 1430 if (param != null) { 1431 return param; 1432 } 1433 throw MondrianResource.instance().UnknownParameter.ex(name); 1434 } 1435 } 1436 1437 private int whichArg(final FunCall node, final Exp arg) { 1438 final Exp[] children = node.getArgs(); 1439 for (int i = 0; i < children.length; i++) { 1440 if (children[i] == arg) { 1441 return i; 1442 } 1443 } 1444 return -1; 1445 } 1446 } 1447 1448 /** 1449 * Source of metadata within the scope of a query. 1450 * 1451 * <p>Note especially that {@link #getCalculatedMember(java.util.List)} 1452 * returns the calculated members defined in this query. 1453 */ 1454 private class QuerySchemaReader extends DelegatingSchemaReader { 1455 1456 public QuerySchemaReader(SchemaReader cubeSchemaReader) { 1457 super(cubeSchemaReader); 1458 } 1459 1460 public Member getMemberByUniqueName( 1461 List<Id.Segment> uniqueNameParts, 1462 boolean failIfNotFound, 1463 MatchType matchType) 1464 { 1465 final String uniqueName = Util.implode(uniqueNameParts); 1466 Member member = lookupMemberFromCache(uniqueName); 1467 if (member == null) { 1468 // Not a calculated member in the query, so go to the cube. 1469 member = schemaReader.getMemberByUniqueName( 1470 uniqueNameParts, failIfNotFound, matchType); 1471 } 1472 if (!failIfNotFound && member == null) { 1473 return null; 1474 } 1475 if (getRole().canAccess(member)) { 1476 return member; 1477 } else { 1478 return null; 1479 } 1480 } 1481 1482 public List<Member> getLevelMembers( 1483 Level level, 1484 boolean includeCalculated) 1485 { 1486 List<Member> members = super.getLevelMembers(level, false); 1487 if (includeCalculated) { 1488 members = Util.addLevelCalculatedMembers(this, level, members); 1489 } 1490 return members; 1491 } 1492 1493 public Member getCalculatedMember(List<Id.Segment> nameParts) { 1494 final String uniqueName = Util.implode(nameParts); 1495 return lookupMemberFromCache(uniqueName); 1496 } 1497 1498 public List<Member> getCalculatedMembers(Hierarchy hierarchy) { 1499 List<Member> result = new ArrayList<Member>(); 1500 // Add calculated members in the cube. 1501 final List<Member> calculatedMembers = 1502 super.getCalculatedMembers(hierarchy); 1503 result.addAll(calculatedMembers); 1504 // Add calculated members defined in the query. 1505 for (Member member : getDefinedMembers()) { 1506 if (member.getHierarchy().equals(hierarchy)) { 1507 result.add(member); 1508 } 1509 } 1510 return result; 1511 } 1512 1513 public List<Member> getCalculatedMembers(Level level) { 1514 List<Member> hierarchyMembers = 1515 getCalculatedMembers(level.getHierarchy()); 1516 List<Member> result = new ArrayList<Member>(); 1517 for (Member member : hierarchyMembers) { 1518 if (member.getLevel().equals(level)) { 1519 result.add(member); 1520 } 1521 } 1522 return result; 1523 } 1524 1525 public List<Member> getCalculatedMembers() { 1526 return getDefinedMembers(); 1527 } 1528 1529 public OlapElement getElementChild(OlapElement parent, Id.Segment s) 1530 { 1531 return getElementChild(parent, s, MatchType.EXACT); 1532 } 1533 1534 public OlapElement getElementChild( 1535 OlapElement parent, Id.Segment s, MatchType matchType) 1536 { 1537 // first look in cube 1538 OlapElement mdxElement = 1539 schemaReader.getElementChild(parent, s, matchType); 1540 if (mdxElement != null) { 1541 return mdxElement; 1542 } 1543 // then look in defined members (removed sf#1084651) 1544 1545 // then in defined sets 1546 for (Formula formula : formulas) { 1547 if (formula.isMember()) { 1548 continue; // have already done these 1549 } 1550 Id id = formula.getIdentifier(); 1551 if (id.getSegments().size() == 1 && 1552 id.getSegments().get(0).matches(s.name)) { 1553 return formula.getNamedSet(); 1554 } 1555 } 1556 1557 return mdxElement; 1558 } 1559 1560 public OlapElement lookupCompound( 1561 OlapElement parent, 1562 List<Id.Segment> names, 1563 boolean failIfNotFound, 1564 int category) 1565 { 1566 return lookupCompound( 1567 parent, names, failIfNotFound, category, MatchType.EXACT); 1568 } 1569 1570 public OlapElement lookupCompound( 1571 OlapElement parent, 1572 List<Id.Segment> names, 1573 boolean failIfNotFound, 1574 int category, 1575 MatchType matchType) 1576 { 1577 // First look to ourselves. 1578 switch (category) { 1579 case Category.Unknown: 1580 case Category.Member: 1581 if (parent == cube) { 1582 final Member calculatedMember = getCalculatedMember(names); 1583 if (calculatedMember != null) { 1584 return calculatedMember; 1585 } 1586 } 1587 } 1588 switch (category) { 1589 case Category.Unknown: 1590 case Category.Set: 1591 if (parent == cube) { 1592 final NamedSet namedSet = getNamedSet(names); 1593 if (namedSet != null) { 1594 return namedSet; 1595 } 1596 } 1597 } 1598 // Then delegate to the next reader. 1599 OlapElement olapElement = super.lookupCompound( 1600 parent, names, failIfNotFound, category, matchType); 1601 if (olapElement instanceof Member) { 1602 Member member = (Member) olapElement; 1603 final Formula formula = (Formula) 1604 member.getPropertyValue(Property.FORMULA.name); 1605 if (formula != null) { 1606 // This is a calculated member defined against the cube. 1607 // Create a free-standing formula using the same 1608 // expression, then use the member defined in that formula. 1609 final Formula formulaClone = (Formula) formula.clone(); 1610 formulaClone.createElement(Query.this); 1611 formulaClone.accept(createValidator()); 1612 olapElement = formulaClone.getMdxMember(); 1613 } 1614 } 1615 return olapElement; 1616 } 1617 1618 public NamedSet getNamedSet(List<Id.Segment> nameParts) { 1619 if (nameParts.size() != 1) { 1620 return null; 1621 } 1622 return lookupNamedSet(nameParts.get(0).name); 1623 } 1624 1625 public Parameter getParameter(String name) { 1626 // Look for a parameter defined in the query. 1627 for (Parameter parameter : parameters) { 1628 if (parameter.getName().equals(name)) { 1629 return parameter; 1630 } 1631 } 1632 1633 // Look for a parameter defined in this connection. 1634 if (Util.lookup(RolapConnectionProperties.class, name) != null) { 1635 Object value = connection.getProperty(name); 1636 // TODO: Don't assume it's a string. 1637 // TODO: Create expression which will get the value from the 1638 // connection at the time the query is executed. 1639 Literal defaultValue = 1640 Literal.createString(String.valueOf(value)); 1641 return new ConnectionParameterImpl(name, defaultValue); 1642 } 1643 1644 return super.getParameter(name); 1645 } 1646 1647 } 1648 1649 private static class ConnectionParameterImpl 1650 extends ParameterImpl 1651 { 1652 public ConnectionParameterImpl(String name, Literal defaultValue) { 1653 super(name, defaultValue, "Connection property", new StringType()); 1654 } 1655 1656 public Scope getScope() { 1657 return Scope.Connection; 1658 } 1659 1660 public void setValue(Object value) { 1661 throw MondrianResource.instance().ParameterIsNotModifiable.ex( 1662 getName(), getScope().name()); 1663 } 1664 } 1665 } 1666 1667 // End Query.java