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) 1998-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.olap; 012 013import mondrian.calc.*; 014import mondrian.mdx.*; 015import mondrian.olap.fun.ParameterFunDef; 016import mondrian.olap.type.*; 017import mondrian.resource.MondrianResource; 018import mondrian.rolap.*; 019import mondrian.server.*; 020import mondrian.spi.ProfileHandler; 021import mondrian.util.ArrayStack; 022 023import org.apache.commons.collections.collection.CompositeCollection; 024 025import org.olap4j.impl.IdentifierParser; 026import org.olap4j.mdx.IdentifierSegment; 027 028import java.io.PrintWriter; 029import java.sql.SQLException; 030import java.util.*; 031 032/** 033 * <code>Query</code> is an MDX query. 034 * 035 * <p>It is created by calling {@link Connection#parseQuery}, 036 * and executed by calling {@link Connection#execute}, 037 * to return a {@link Result}.</p> 038 * 039 * <h3>Query control</h3> 040 * 041 * <p>Most queries are model citizens, executing quickly (often using cached 042 * results from previous queries), but some queries take more time, or more 043 * database resources, or more results, than is reasonable. Mondrian offers 044 * three ways to control rogue queries:<ul> 045 * 046 * <li>You can set a query timeout by setting the 047 * {@link MondrianProperties#QueryTimeout} parameter. If the query 048 * takes longer to execute than the value of this parameter, the system 049 * will kill it.</li> 050 * 051 * <li>The {@link MondrianProperties#QueryLimit} parameter limits the number 052 * of cells returned by a query.</li> 053 * 054 * <li>At any time while a query is executing, another thread can cancel the 055 * query by calling 056 * {@link #getStatement()}.{@link Statement#cancel() cancel()}. 057 * The call to {@link Connection#execute(Query)} 058 * will throw an exception.</li> 059 * 060 * </ul> 061 * 062 * @author jhyde, 20 January, 1999 063 */ 064public class Query extends QueryPart { 065 066 private Formula[] formulas; 067 068 /** 069 * public-private: This must be public because it is still accessed in 070 * rolap.RolapConnection 071 */ 072 public QueryAxis[] axes; 073 074 private QueryAxis slicerAxis; 075 076 /** 077 * Definitions of all parameters used in this query. 078 */ 079 private final List<Parameter> parameters = new ArrayList<Parameter>(); 080 081 private final Map<String, Parameter> parametersByName = 082 new HashMap<String, Parameter>(); 083 084 /** 085 * Cell properties. Not currently used. 086 */ 087 private final QueryPart[] cellProps; 088 089 /** 090 * Cube this query belongs to. 091 */ 092 private final Cube cube; 093 094 private final Statement statement; 095 public Calc[] axisCalcs; 096 public Calc slicerCalc; 097 098 /** 099 * Set of FunDefs for which alerts about non-native evaluation 100 * have already been posted. 101 */ 102 Set<FunDef> alertedNonNativeFunDefs; 103 104 /** 105 * Unique list of members referenced from the measures dimension. 106 * Will be used to determine if cross joins can be processed natively 107 * for virtual cubes. 108 */ 109 private Set<Member> measuresMembers; 110 111 /** 112 * If true, virtual cubes can be processed using native cross joins. 113 * It defaults to true, unless functions are applied on measures. 114 */ 115 private boolean nativeCrossJoinVirtualCube; 116 117 /** 118 * Used for virtual cubes. 119 * Comtains a list of base cubes related to a virtual cube 120 */ 121 private List<RolapCube> baseCubes; 122 123 /** 124 * If true, enforce validation even when ignoreInvalidMembers is set. 125 */ 126 private boolean strictValidation; 127 128 /** 129 * How should the query be returned? Valid values are: 130 * ResultStyle.ITERABLE 131 * ResultStyle.LIST 132 * ResultStyle.MUTABLE_LIST 133 * For java4, use LIST 134 */ 135 private ResultStyle resultStyle = 136 Util.Retrowoven ? ResultStyle.LIST : ResultStyle.ITERABLE; 137 138 private Map<String, Object> evalCache = new HashMap<String, Object>(); 139 140 /** 141 * List of aliased expressions defined in this query, and where they are 142 * defined. There might be more than one aliased expression with the same 143 * name. 144 */ 145 private final List<ScopedNamedSet> scopedNamedSets = 146 new ArrayList<ScopedNamedSet>(); 147 private boolean ownStatement; 148 149 /** 150 * Creates a Query. 151 */ 152 public Query( 153 Statement statement, 154 Formula[] formulas, 155 QueryAxis[] axes, 156 String cube, 157 QueryAxis slicerAxis, 158 QueryPart[] cellProps, 159 boolean strictValidation) 160 { 161 this( 162 statement, 163 Util.lookupCube(statement.getSchemaReader(), cube, true), 164 formulas, 165 axes, 166 slicerAxis, 167 cellProps, 168 new Parameter[0], 169 strictValidation); 170 } 171 172 /** 173 * Creates a Query. 174 */ 175 public Query( 176 Statement statement, 177 Cube mdxCube, 178 Formula[] formulas, 179 QueryAxis[] axes, 180 QueryAxis slicerAxis, 181 QueryPart[] cellProps, 182 Parameter[] parameters, 183 boolean strictValidation) 184 { 185 this.statement = statement; 186 this.cube = mdxCube; 187 this.formulas = formulas; 188 this.axes = axes; 189 normalizeAxes(); 190 this.slicerAxis = slicerAxis; 191 this.cellProps = cellProps; 192 this.parameters.addAll(Arrays.asList(parameters)); 193 this.measuresMembers = new HashSet<Member>(); 194 // assume, for now, that cross joins on virtual cubes can be 195 // processed natively; as we parse the query, we'll know otherwise 196 this.nativeCrossJoinVirtualCube = true; 197 this.strictValidation = strictValidation; 198 this.alertedNonNativeFunDefs = new HashSet<FunDef>(); 199 statement.setQuery(this); 200 resolve(); 201 202 if (RolapUtil.PROFILE_LOGGER.isDebugEnabled() 203 && statement.getProfileHandler() == null) 204 { 205 statement.enableProfiling( 206 new ProfileHandler() { 207 public void explain(String plan, QueryTiming timing) { 208 if (timing != null) { 209 plan += "\n" + timing; 210 } 211 RolapUtil.PROFILE_LOGGER.debug(plan); 212 } 213 } 214 ); 215 } 216 } 217 218 /** 219 * Sets the timeout in milliseconds of this Query. 220 * 221 * <p>Zero means no timeout. 222 * 223 * @param queryTimeoutMillis Timeout in milliseconds 224 * 225 * @deprecated This method will be removed in mondrian-4.0 226 */ 227 public void setQueryTimeoutMillis(long queryTimeoutMillis) { 228 statement.setQueryTimeoutMillis(queryTimeoutMillis); 229 } 230 231 /** 232 * Checks whether the property name is present in the query. 233 */ 234 public boolean hasCellProperty(String propertyName) { 235 for (QueryPart cellProp : cellProps) { 236 if (((CellProperty)cellProp).isNameEquals(propertyName)) { 237 return true; 238 } 239 } 240 return false; 241 } 242 243 /** 244 * Checks whether any cell property present in the query 245 */ 246 public boolean isCellPropertyEmpty() { 247 return cellProps.length == 0; 248 } 249 250 /** 251 * Adds a new formula specifying a set 252 * to an existing query. 253 */ 254 public void addFormula(Id id, Exp exp) { 255 addFormula( 256 new Formula(false, id, exp, new MemberProperty[0], null, null)); 257 } 258 259 /** 260 * Adds a new formula specifying a member 261 * to an existing query. 262 * 263 * @param id Name of member 264 * @param exp Expression for member 265 * @param memberProperties Properties of member 266 */ 267 public void addFormula( 268 Id id, 269 Exp exp, 270 MemberProperty[] memberProperties) 271 { 272 addFormula(new Formula(true, id, exp, memberProperties, null, null)); 273 } 274 275 /** 276 * Adds a new formula specifying a member or a set 277 * to an existing query; resolve is called after 278 * the formula has been added. 279 * 280 * @param formula Formula to add to query 281 */ 282 public void addFormula(Formula formula) { 283 formulas = Util.append(formulas, formula); 284 resolve(); 285 } 286 287 /** 288 * Adds some number of new formulas specifying members 289 * or sets to an existing query; resolve is only called 290 * once, after all the new members have been added to 291 * the query. 292 * 293 * @param additions Formulas to add to query 294 */ 295 public void addFormulas(Formula... additions) { 296 formulas = Util.appendArrays(formulas, additions); 297 resolve(); 298 } 299 300 /** 301 * Creates a validator for this query. 302 * 303 * @return Validator 304 */ 305 public Validator createValidator() { 306 return createValidator( 307 statement.getSchema().getFunTable(), 308 false); 309 } 310 311 /** 312 * Creates a validator for this query that uses a given function table and 313 * function validation policy. 314 * 315 * @param functionTable Function table 316 * @param alwaysResolveFunDef Whether to always resolve function 317 * definitions (see {@link Validator#alwaysResolveFunDef()}) 318 * @return Validator 319 */ 320 public Validator createValidator( 321 FunTable functionTable, 322 boolean alwaysResolveFunDef) 323 { 324 return new QueryValidator( 325 functionTable, 326 alwaysResolveFunDef, 327 Query.this); 328 } 329 330 /** 331 * @deprecated Please use {@link #clone}; this method will be removed in 332 * mondrian-4.0 333 */ 334 public Query safeClone() { 335 return clone(); 336 } 337 338 @SuppressWarnings({ 339 "CloneDoesntCallSuperClone", 340 "CloneDoesntDeclareCloneNotSupportedException" 341 }) 342 public Query clone() { 343 return new Query( 344 statement, 345 cube, 346 Formula.cloneArray(formulas), 347 QueryAxis.cloneArray(axes), 348 (slicerAxis == null) ? null : (QueryAxis) slicerAxis.clone(), 349 cellProps, 350 parameters.toArray(new Parameter[parameters.size()]), 351 strictValidation); 352 } 353 354 public Connection getConnection() { 355 return statement.getMondrianConnection(); 356 } 357 358 /** 359 * Issues a cancel request on this Query object. Once the thread 360 * running the query detects the cancel request, the query execution will 361 * throw an exception. See <code>BasicQueryTest.testCancel</code> for an 362 * example of usage of this method. 363 * 364 * @deprecated This method is deprecated and will be removed in mondrian-4.0 365 */ 366 public void cancel() { 367 try { 368 statement.cancel(); 369 } catch (SQLException e) { 370 throw new RuntimeException(e); 371 } 372 } 373 374 /** 375 * Checks if either a cancel request has been issued on the query or 376 * the execution time has exceeded the timeout value (if one has been 377 * set). Exceptions are raised if either of these two conditions are 378 * met. This method should be called periodically during query execution 379 * to ensure timely detection of these events, particularly before/after 380 * any potentially long running operations. 381 * 382 * @deprecated This method will be removed in mondrian-4.0 383 */ 384 public void checkCancelOrTimeout() { 385 final Execution execution0 = statement.getCurrentExecution(); 386 if (execution0 == null) { 387 return; 388 } 389 execution0.checkCancelOrTimeout(); 390 } 391 392 /** 393 * Gets the query start time 394 * @return start time 395 * 396 * @deprecated Use {@link Execution#getStartTime}. This method is deprecated 397 * and will be removed in mondrian-4.0 398 */ 399 public long getQueryStartTime() { 400 final Execution currentExecution = statement.getCurrentExecution(); 401 return currentExecution == null 402 ? 0 403 : currentExecution.getStartTime(); 404 } 405 406 /** 407 * Determines whether an alert for non-native evaluation needs 408 * to be posted. 409 * 410 * @param funDef function type to alert for 411 * 412 * @return true if alert should be raised 413 */ 414 public boolean shouldAlertForNonNative(FunDef funDef) { 415 return alertedNonNativeFunDefs.add(funDef); 416 } 417 418 private void normalizeAxes() { 419 for (int i = 0; i < axes.length; i++) { 420 AxisOrdinal correctOrdinal = 421 AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(i); 422 if (axes[i].getAxisOrdinal() != correctOrdinal) { 423 for (int j = i + 1; j < axes.length; j++) { 424 if (axes[j].getAxisOrdinal() == correctOrdinal) { 425 // swap axes 426 QueryAxis temp = axes[i]; 427 axes[i] = axes[j]; 428 axes[j] = temp; 429 break; 430 } 431 } 432 } 433 } 434 } 435 436 /** 437 * Performs type-checking and validates internal consistency of a query, 438 * using the default resolver. 439 * 440 * <p>This method is called automatically when a query is created; you need 441 * to call this method manually if you have modified the query's expression 442 * tree in any way. 443 */ 444 public void resolve() { 445 final Validator validator = createValidator(); 446 resolve(validator); // resolve self and children 447 // Create a dummy result so we can use its evaluator 448 final Evaluator evaluator = RolapUtil.createEvaluator(statement); 449 ExpCompiler compiler = 450 createCompiler( 451 evaluator, validator, Collections.singletonList(resultStyle)); 452 compile(compiler); 453 } 454 455 /** 456 * @return true if the relevant property for ignoring invalid members is 457 * set to true for this query's environment (a different property is 458 * checked depending on whether environment is schema load vs query 459 * validation) 460 */ 461 public boolean ignoreInvalidMembers() 462 { 463 MondrianProperties props = MondrianProperties.instance(); 464 final boolean load = ((RolapCube) getCube()).isLoadInProgress(); 465 return 466 !strictValidation 467 && (load 468 ? props.IgnoreInvalidMembers.get() 469 : props.IgnoreInvalidMembersDuringQuery.get()); 470 } 471 472 /** 473 * A Query's ResultStyle can only be one of the following: 474 * ResultStyle.ITERABLE 475 * ResultStyle.LIST 476 * ResultStyle.MUTABLE_LIST 477 */ 478 public void setResultStyle(ResultStyle resultStyle) { 479 switch (resultStyle) { 480 case ITERABLE: 481 // For java4, use LIST 482 this.resultStyle = (Util.Retrowoven) 483 ? ResultStyle.LIST : ResultStyle.ITERABLE; 484 break; 485 case LIST: 486 case MUTABLE_LIST: 487 this.resultStyle = resultStyle; 488 break; 489 default: 490 throw ResultStyleException.generateBadType( 491 ResultStyle.ITERABLE_LIST_MUTABLELIST, 492 resultStyle); 493 } 494 } 495 496 public ResultStyle getResultStyle() { 497 return resultStyle; 498 } 499 500 /** 501 * Generates compiled forms of all expressions. 502 * 503 * @param compiler Compiler 504 */ 505 private void compile(ExpCompiler compiler) { 506 if (formulas != null) { 507 for (Formula formula : formulas) { 508 formula.compile(); 509 } 510 } 511 512 if (axes != null) { 513 axisCalcs = new Calc[axes.length]; 514 for (int i = 0; i < axes.length; i++) { 515 axisCalcs[i] = axes[i].compile(compiler, resultStyle); 516 } 517 } 518 if (slicerAxis != null) { 519 slicerCalc = slicerAxis.compile(compiler, resultStyle); 520 } 521 } 522 523 /** 524 * Performs type-checking and validates internal consistency of a query. 525 * 526 * @param validator Validator 527 */ 528 public void resolve(Validator validator) { 529 // Before commencing validation, create all calculated members, 530 // calculated sets, and parameters. 531 if (formulas != null) { 532 // Resolving of formulas should be done in two parts 533 // because formulas might depend on each other, so all calculated 534 // mdx elements have to be defined during resolve. 535 for (Formula formula : formulas) { 536 formula.createElement(validator.getQuery()); 537 } 538 } 539 540 // Register all parameters. 541 parameters.clear(); 542 parametersByName.clear(); 543 accept(new ParameterFinder()); 544 545 // Register all aliased expressions ('expr AS alias') as named sets. 546 accept(new AliasedExpressionFinder()); 547 548 // Validate formulas. 549 if (formulas != null) { 550 for (Formula formula : formulas) { 551 validator.validate(formula); 552 } 553 } 554 555 // Validate axes. 556 if (axes != null) { 557 Set<Integer> axisNames = new HashSet<Integer>(); 558 for (QueryAxis axis : axes) { 559 validator.validate(axis); 560 if (!axisNames.add(axis.getAxisOrdinal().logicalOrdinal())) { 561 throw MondrianResource.instance().DuplicateAxis.ex( 562 axis.getAxisName()); 563 } 564 } 565 566 // Make sure that there are no gaps. If there are N axes, then axes 567 // 0 .. N-1 should exist. 568 int seekOrdinal = 569 AxisOrdinal.StandardAxisOrdinal.COLUMNS.logicalOrdinal(); 570 for (QueryAxis axis : axes) { 571 if (!axisNames.contains(seekOrdinal)) { 572 AxisOrdinal axisName = 573 AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal( 574 seekOrdinal); 575 throw MondrianResource.instance().NonContiguousAxis.ex( 576 seekOrdinal, 577 axisName.name()); 578 } 579 ++seekOrdinal; 580 } 581 } 582 if (slicerAxis != null) { 583 slicerAxis.validate(validator); 584 } 585 586 // Make sure that no hierarchy is used on more than one axis. 587 for (Hierarchy hierarchy : ((RolapCube) getCube()).getHierarchies()) { 588 int useCount = 0; 589 for (QueryAxis axis : allAxes()) { 590 if (axis.getSet().getType().usesHierarchy(hierarchy, true)) { 591 ++useCount; 592 } 593 } 594 if (useCount > 1) { 595 throw MondrianResource.instance().HierarchyInIndependentAxes.ex( 596 hierarchy.getUniqueName()); 597 } 598 } 599 } 600 601 @Override 602 public void explain(PrintWriter pw) { 603 final boolean profiling = getStatement().getProfileHandler() != null; 604 final CalcWriter calcWriter = new CalcWriter(pw, profiling); 605 for (Formula formula : formulas) { 606 formula.getMdxMember(); // TODO: 607 } 608 if (slicerCalc != null) { 609 pw.println("Axis (FILTER):"); 610 slicerCalc.accept(calcWriter); 611 pw.println(); 612 } 613 int i = -1; 614 for (QueryAxis axis : axes) { 615 ++i; 616 pw.println("Axis (" + axis.getAxisName() + "):"); 617 axisCalcs[i].accept(calcWriter); 618 pw.println(); 619 } 620 pw.flush(); 621 } 622 623 /** 624 * Returns a collection of all axes, including the slicer as the first 625 * element, if there is a slicer. 626 * 627 * @return Collection of all axes including slicer 628 */ 629 private Collection<QueryAxis> allAxes() { 630 if (slicerAxis == null) { 631 return Arrays.asList(axes); 632 } else { 633 //noinspection unchecked 634 return new CompositeCollection( 635 new Collection[] { 636 Collections.singletonList(slicerAxis), 637 Arrays.asList(axes)}); 638 } 639 } 640 641 public void unparse(PrintWriter pw) { 642 if (formulas != null) { 643 for (int i = 0; i < formulas.length; i++) { 644 if (i == 0) { 645 pw.print("with "); 646 } else { 647 pw.print(" "); 648 } 649 formulas[i].unparse(pw); 650 pw.println(); 651 } 652 } 653 pw.print("select "); 654 if (axes != null) { 655 for (int i = 0; i < axes.length; i++) { 656 axes[i].unparse(pw); 657 if (i < axes.length - 1) { 658 pw.println(","); 659 pw.print(" "); 660 } else { 661 pw.println(); 662 } 663 } 664 } 665 if (cube != null) { 666 pw.println("from [" + cube.getName() + "]"); 667 } 668 if (slicerAxis != null) { 669 pw.print("where "); 670 slicerAxis.unparse(pw); 671 pw.println(); 672 } 673 } 674 675 /** Returns the MDX query string. */ 676 public String toString() { 677 resolve(); 678 return Util.unparse(this); 679 } 680 681 public Object[] getChildren() { 682 // Chidren are axes, slicer, and formulas (in that order, to be 683 // consistent with replaceChild). 684 List<QueryPart> list = new ArrayList<QueryPart>(); 685 list.addAll(Arrays.asList(axes)); 686 if (slicerAxis != null) { 687 list.add(slicerAxis); 688 } 689 list.addAll(Arrays.asList(formulas)); 690 return list.toArray(); 691 } 692 693 public QueryAxis getSlicerAxis() { 694 return slicerAxis; 695 } 696 697 public void setSlicerAxis(QueryAxis axis) { 698 this.slicerAxis = axis; 699 } 700 701 /** 702 * Adds a level to an axis expression. 703 */ 704 public void addLevelToAxis(AxisOrdinal axis, Level level) { 705 assert axis != null; 706 axes[axis.logicalOrdinal()].addLevel(level); 707 } 708 709 /** 710 * Returns the hierarchies in an expression. 711 * 712 * <p>If the expression's type is a dimension with several hierarchies, 713 * assumes that the expression yields a member of the first (default) 714 * hierarchy of the dimension. 715 * 716 * <p>For example, the expression 717 * <blockquote><code>Crossjoin( 718 * Hierarchize( 719 * Union( 720 * {[Time].LastSibling}, [Time].LastSibling.Children)), 721 * {[Measures].[Unit Sales], [Measures].[Store Cost]})</code> 722 * </blockquote> 723 * 724 * has type <code>{[Time.Monthly], [Measures]}</code> even though 725 * <code>[Time].LastSibling</code> might return a member of either 726 * [Time.Monthly] or [Time.Weekly]. 727 */ 728 private Hierarchy[] collectHierarchies(Exp queryPart) { 729 Type exprType = queryPart.getType(); 730 if (exprType instanceof SetType) { 731 exprType = ((SetType) exprType).getElementType(); 732 } 733 if (exprType instanceof TupleType) { 734 final Type[] types = ((TupleType) exprType).elementTypes; 735 ArrayList<Hierarchy> hierarchyList = new ArrayList<Hierarchy>(); 736 for (Type type : types) { 737 hierarchyList.add(getTypeHierarchy(type)); 738 } 739 return hierarchyList.toArray(new Hierarchy[hierarchyList.size()]); 740 } 741 return new Hierarchy[] {getTypeHierarchy(exprType)}; 742 } 743 744 private Hierarchy getTypeHierarchy(final Type type) { 745 Hierarchy hierarchy = type.getHierarchy(); 746 if (hierarchy != null) { 747 return hierarchy; 748 } 749 final Dimension dimension = type.getDimension(); 750 if (dimension != null) { 751 return dimension.getHierarchy(); 752 } 753 return null; 754 } 755 756 /** 757 * Assigns a value to the parameter with a given name. 758 * 759 * @throws RuntimeException if there is not parameter with the given name 760 */ 761 public void setParameter(final String parameterName, final Object value) { 762 // Need to resolve query before we set parameters, in order to create 763 // slots to store them in. (This code will go away when parameters 764 // belong to prepared statements.) 765 if (parameters.isEmpty()) { 766 resolve(); 767 } 768 769 final Parameter param = 770 getSchemaReader(false).getParameter(parameterName); 771 if (param == null) { 772 throw MondrianResource.instance().UnknownParameter.ex( 773 parameterName); 774 } 775 if (!param.isModifiable()) { 776 throw MondrianResource.instance().ParameterIsNotModifiable.ex( 777 parameterName, param.getScope().name()); 778 } 779 final Object value2 = 780 Locus.execute( 781 new Execution(statement, 0), 782 "Query.quickParse", 783 new Locus.Action<Object>() { 784 public Object execute() { 785 return quickParse( 786 parameterName, param.getType(), value, Query.this); 787 } 788 } 789 ); 790 param.setValue(value2); 791 } 792 793 /** 794 * Converts a value into something appropriate for a given type. 795 * 796 * <p>Viz: 797 * <ul> 798 * <li>For numerics, takes number or string and returns a {@link Number}. 799 * <li>For strings, takes string, or calls {@link Object#toString()} on any 800 * other type 801 * <li>For members, takes member or string 802 * <li>For sets of members, requires a list of members or strings and 803 * converts each element to a member. 804 * </ul> 805 * 806 * @param type Type 807 * @param value Value 808 * @param query Query 809 * @return Value of appropriate type 810 * @throws NumberFormatException If value needs to be a number but isn't 811 */ 812 private static Object quickParse( 813 String parameterName, 814 Type type, 815 Object value, 816 Query query) 817 throws NumberFormatException 818 { 819 int category = TypeUtil.typeToCategory(type); 820 switch (category) { 821 case Category.Numeric: 822 if (value instanceof Number || value == null) { 823 return value; 824 } 825 if (value instanceof String) { 826 String s = (String) value; 827 try { 828 return new Integer(s); 829 } catch (NumberFormatException e) { 830 return new Double(s); 831 } 832 } 833 throw Util.newInternal( 834 "Invalid value '" + value + "' for parameter '" + parameterName 835 + "', type " + type); 836 case Category.String: 837 if (value == null) { 838 return null; 839 } 840 return value.toString(); 841 case Category.Set: 842 if (value instanceof String) { 843 value = IdentifierParser.parseIdentifierList((String) value); 844 } 845 if (!(value instanceof List)) { 846 throw Util.newInternal( 847 "Invalid value '" + value + "' for parameter '" 848 + parameterName + "', type " + type); 849 } 850 List<Member> expList = new ArrayList<Member>(); 851 final List list = (List) value; 852 final SetType setType = (SetType) type; 853 final Type elementType = setType.getElementType(); 854 for (Object o : list) { 855 // In keeping with MDX semantics, null members are omitted from 856 // lists. 857 if (o == null) { 858 continue; 859 } 860 final Member member = 861 (Member) quickParse(parameterName, elementType, o, query); 862 expList.add(member); 863 } 864 return expList; 865 case Category.Member: 866 if (value == null) { 867 // Setting a member parameter to null is the same as setting to 868 // the null member of the hierarchy. May not be equivalent to 869 // the default value of the parameter, nor the same as the all 870 // member. 871 if (type.getHierarchy() != null) { 872 value = type.getHierarchy().getNullMember(); 873 } else if (type.getDimension() != null) { 874 value = type.getDimension().getHierarchy().getNullMember(); 875 } 876 } 877 if (value instanceof String) { 878 value = Util.parseIdentifier((String) value); 879 } 880 if (value instanceof List 881 && Util.canCast((List) value, Id.Segment.class)) 882 { 883 final List<Id.Segment> segmentList = Util.cast((List) value); 884 final OlapElement olapElement = Util.lookup(query, segmentList); 885 if (olapElement instanceof Member) { 886 value = olapElement; 887 } 888 } 889 if (value instanceof List 890 && Util.canCast((List) value, IdentifierSegment.class)) 891 { 892 final List<IdentifierSegment> olap4jSegmentList = 893 Util.cast((List) value); 894 final List<Id.Segment> segmentList = 895 Util.convert(olap4jSegmentList); 896 final OlapElement olapElement = Util.lookup(query, segmentList); 897 if (olapElement instanceof Member) { 898 value = olapElement; 899 } 900 } 901 if (value instanceof Member) { 902 if (type.isInstance(value)) { 903 return value; 904 } 905 } 906 throw Util.newInternal( 907 "Invalid value '" + value + "' for parameter '" 908 + parameterName + "', type " + type); 909 default: 910 throw Category.instance.badValue(category); 911 } 912 } 913 914 /** 915 * Swaps the x- and y- axes. 916 * Does nothing if the number of axes != 2. 917 */ 918 public void swapAxes() { 919 if (axes.length == 2) { 920 Exp e0 = axes[0].getSet(); 921 boolean nonEmpty0 = axes[0].isNonEmpty(); 922 Exp e1 = axes[1].getSet(); 923 boolean nonEmpty1 = axes[1].isNonEmpty(); 924 axes[1].setSet(e0); 925 axes[1].setNonEmpty(nonEmpty0); 926 axes[0].setSet(e1); 927 axes[0].setNonEmpty(nonEmpty1); 928 // showSubtotals ??? 929 } 930 } 931 932 /** 933 * Returns the parameters defined in this query. 934 */ 935 public Parameter[] getParameters() { 936 return parameters.toArray(new Parameter[parameters.size()]); 937 } 938 939 public Cube getCube() { 940 return cube; 941 } 942 943 /** 944 * Returns a schema reader. 945 * 946 * @param accessControlled If true, schema reader returns only elements 947 * which are accessible to the statement's current role 948 * 949 * @return schema reader 950 */ 951 public SchemaReader getSchemaReader(boolean accessControlled) { 952 final Role role; 953 if (accessControlled) { 954 // full access control 955 role = getConnection().getRole(); 956 } else { 957 role = null; 958 } 959 final SchemaReader cubeSchemaReader = cube.getSchemaReader(role); 960 return new QuerySchemaReader(cubeSchemaReader, Query.this); 961 } 962 963 /** 964 * Looks up a member whose unique name is <code>memberUniqueName</code> 965 * from cache. If the member is not in cache, returns null. 966 */ 967 public Member lookupMemberFromCache(String memberUniqueName) { 968 // first look in defined members 969 for (Member member : getDefinedMembers()) { 970 if (Util.equalName(member.getUniqueName(), memberUniqueName) 971 || Util.equalName( 972 getUniqueNameWithoutAll(member), 973 memberUniqueName)) 974 { 975 return member; 976 } 977 } 978 return null; 979 } 980 981 private String getUniqueNameWithoutAll(Member member) { 982 // build unique string 983 Member parentMember = member.getParentMember(); 984 if ((parentMember != null) && !parentMember.isAll()) { 985 return Util.makeFqName( 986 getUniqueNameWithoutAll(parentMember), 987 member.getName()); 988 } else { 989 return Util.makeFqName(member.getHierarchy(), member.getName()); 990 } 991 } 992 993 /** 994 * Looks up a named set. 995 */ 996 private NamedSet lookupNamedSet(Id.Segment segment) { 997 if (!(segment instanceof Id.NameSegment)) { 998 return null; 999 } 1000 Id.NameSegment nameSegment = (Id.NameSegment) segment; 1001 for (Formula formula : formulas) { 1002 if (!formula.isMember() 1003 && formula.getElement() != null 1004 && formula.getName().equals(nameSegment.getName())) 1005 { 1006 return (NamedSet) formula.getElement(); 1007 } 1008 } 1009 return null; 1010 } 1011 1012 /** 1013 * Creates a named set defined by an alias. 1014 */ 1015 public ScopedNamedSet createScopedNamedSet( 1016 String name, 1017 QueryPart scope, 1018 Exp expr) 1019 { 1020 final ScopedNamedSet scopedNamedSet = 1021 new ScopedNamedSet( 1022 name, scope, expr); 1023 scopedNamedSets.add(scopedNamedSet); 1024 return scopedNamedSet; 1025 } 1026 1027 /** 1028 * Looks up a named set defined by an alias. 1029 * 1030 * @param nameParts Multi-part identifier for set 1031 * @param scopeList Parse tree node where name is used (last in list) and 1032 */ 1033 ScopedNamedSet lookupScopedNamedSet( 1034 List<Id.Segment> nameParts, 1035 ArrayStack<QueryPart> scopeList) 1036 { 1037 if (nameParts.size() != 1) { 1038 return null; 1039 } 1040 if (!(nameParts.get(0) instanceof Id.NameSegment)) { 1041 return null; 1042 } 1043 String name = ((Id.NameSegment) nameParts.get(0)).getName(); 1044 ScopedNamedSet bestScopedNamedSet = null; 1045 int bestScopeOrdinal = -1; 1046 for (ScopedNamedSet scopedNamedSet : scopedNamedSets) { 1047 if (Util.equalName(scopedNamedSet.name, name)) { 1048 int scopeOrdinal = scopeList.indexOf(scopedNamedSet.scope); 1049 if (scopeOrdinal > bestScopeOrdinal) { 1050 bestScopedNamedSet = scopedNamedSet; 1051 bestScopeOrdinal = scopeOrdinal; 1052 } 1053 } 1054 } 1055 return bestScopedNamedSet; 1056 } 1057 1058 /** 1059 * Returns an array of the formulas used in this query. 1060 */ 1061 public Formula[] getFormulas() { 1062 return formulas; 1063 } 1064 1065 /** 1066 * Returns an array of this query's axes. 1067 */ 1068 public QueryAxis[] getAxes() { 1069 return axes; 1070 } 1071 1072 /** 1073 * Remove a formula from the query. If <code>failIfUsedInQuery</code> is 1074 * true, checks and throws an error if formula is used somewhere in the 1075 * query. 1076 */ 1077 public void removeFormula(String uniqueName, boolean failIfUsedInQuery) { 1078 Formula formula = findFormula(uniqueName); 1079 if (failIfUsedInQuery && formula != null) { 1080 OlapElement mdxElement = formula.getElement(); 1081 // search the query tree to see if this formula expression is used 1082 // anywhere (on the axes or in another formula) 1083 Walker walker = new Walker(this); 1084 while (walker.hasMoreElements()) { 1085 Object queryElement = walker.nextElement(); 1086 if (!queryElement.equals(mdxElement)) { 1087 continue; 1088 } 1089 // mdxElement is used in the query. lets find on on which axis 1090 // or formula 1091 String formulaType = formula.isMember() 1092 ? MondrianResource.instance().CalculatedMember.str() 1093 : MondrianResource.instance().CalculatedSet.str(); 1094 1095 int i = 0; 1096 Object parent = walker.getAncestor(i); 1097 Object grandParent = walker.getAncestor(i + 1); 1098 while ((parent != null) && (grandParent != null)) { 1099 if (grandParent instanceof Query) { 1100 if (parent instanceof Axis) { 1101 throw MondrianResource.instance() 1102 .MdxCalculatedFormulaUsedOnAxis.ex( 1103 formulaType, 1104 uniqueName, 1105 ((QueryAxis) parent).getAxisName()); 1106 1107 } else if (parent instanceof Formula) { 1108 String parentFormulaType = 1109 ((Formula) parent).isMember() 1110 ? MondrianResource.instance() 1111 .CalculatedMember.str() 1112 : MondrianResource.instance() 1113 .CalculatedSet.str(); 1114 throw MondrianResource.instance() 1115 .MdxCalculatedFormulaUsedInFormula.ex( 1116 formulaType, uniqueName, parentFormulaType, 1117 ((Formula) parent).getUniqueName()); 1118 1119 } else { 1120 throw MondrianResource.instance() 1121 .MdxCalculatedFormulaUsedOnSlicer.ex( 1122 formulaType, uniqueName); 1123 } 1124 } 1125 ++i; 1126 parent = walker.getAncestor(i); 1127 grandParent = walker.getAncestor(i + 1); 1128 } 1129 throw MondrianResource.instance() 1130 .MdxCalculatedFormulaUsedInQuery.ex( 1131 formulaType, uniqueName, Util.unparse(this)); 1132 } 1133 } 1134 1135 // remove formula from query 1136 List<Formula> formulaList = new ArrayList<Formula>(); 1137 for (Formula formula1 : formulas) { 1138 if (!formula1.getUniqueName().equalsIgnoreCase(uniqueName)) { 1139 formulaList.add(formula1); 1140 } 1141 } 1142 1143 // it has been found and removed 1144 this.formulas = formulaList.toArray(new Formula[formulaList.size()]); 1145 } 1146 1147 /** 1148 * Returns whether a formula can safely be removed from the query. It can be 1149 * removed if the member or set it defines it not used anywhere else in the 1150 * query, including in another formula. 1151 * 1152 * @param uniqueName Unique name of the member or set defined by the formula 1153 * @return whether the formula can safely be removed 1154 */ 1155 public boolean canRemoveFormula(String uniqueName) { 1156 Formula formula = findFormula(uniqueName); 1157 if (formula == null) { 1158 return false; 1159 } 1160 1161 OlapElement mdxElement = formula.getElement(); 1162 // Search the query tree to see if this formula expression is used 1163 // anywhere (on the axes or in another formula). 1164 Walker walker = new Walker(this); 1165 while (walker.hasMoreElements()) { 1166 Object queryElement = walker.nextElement(); 1167 if (queryElement instanceof MemberExpr 1168 && ((MemberExpr) queryElement).getMember().equals(mdxElement)) 1169 { 1170 return false; 1171 } 1172 if (queryElement instanceof NamedSetExpr 1173 && ((NamedSetExpr) queryElement).getNamedSet().equals( 1174 mdxElement)) 1175 { 1176 return false; 1177 } 1178 } 1179 return true; 1180 } 1181 1182 /** 1183 * Looks up a calculated member or set defined in this Query. 1184 * 1185 * @param uniqueName Unique name of calculated member or set 1186 * @return formula defining calculated member, or null if not found 1187 */ 1188 public Formula findFormula(String uniqueName) { 1189 for (Formula formula : formulas) { 1190 if (formula.getUniqueName().equalsIgnoreCase(uniqueName)) { 1191 return formula; 1192 } 1193 } 1194 return null; 1195 } 1196 1197 /** 1198 * Finds formula by name and renames it to new name. 1199 */ 1200 public void renameFormula(String uniqueName, String newName) { 1201 Formula formula = findFormula(uniqueName); 1202 if (formula == null) { 1203 throw MondrianResource.instance().MdxFormulaNotFound.ex( 1204 "formula", uniqueName, Util.unparse(this)); 1205 } 1206 formula.rename(newName); 1207 } 1208 1209 List<Member> getDefinedMembers() { 1210 List<Member> definedMembers = new ArrayList<Member>(); 1211 for (final Formula formula : formulas) { 1212 if (formula.isMember() 1213 && formula.getElement() != null 1214 && getConnection().getRole().canAccess(formula.getElement())) 1215 { 1216 definedMembers.add((Member) formula.getElement()); 1217 } 1218 } 1219 return definedMembers; 1220 } 1221 1222 /** 1223 * Finds axis by index and sets flag to show empty cells on that axis. 1224 */ 1225 public void setAxisShowEmptyCells(int axis, boolean showEmpty) { 1226 if (axis >= axes.length) { 1227 throw MondrianResource.instance().MdxAxisShowSubtotalsNotSupported 1228 .ex(axis); 1229 } 1230 axes[axis].setNonEmpty(!showEmpty); 1231 } 1232 1233 /** 1234 * Returns <code>Hierarchy[]</code> used on <code>axis</code>. It calls 1235 * {@link #collectHierarchies}. 1236 */ 1237 public Hierarchy[] getMdxHierarchiesOnAxis(AxisOrdinal axis) { 1238 if (axis.logicalOrdinal() >= axes.length) { 1239 throw MondrianResource.instance().MdxAxisShowSubtotalsNotSupported 1240 .ex(axis.logicalOrdinal()); 1241 } 1242 QueryAxis queryAxis = 1243 axis.isFilter() 1244 ? slicerAxis 1245 : axes[axis.logicalOrdinal()]; 1246 return collectHierarchies(queryAxis.getSet()); 1247 } 1248 1249 /** 1250 * Compiles an expression, using a cached compiled expression if available. 1251 * 1252 * @param exp Expression 1253 * @param scalar Whether expression is scalar 1254 * @param resultStyle Preferred result style; if null, use query's default 1255 * result style; ignored if expression is scalar 1256 * @return compiled expression 1257 */ 1258 public Calc compileExpression( 1259 Exp exp, 1260 boolean scalar, 1261 ResultStyle resultStyle) 1262 { 1263 // REVIEW: Set query on a connection's shared internal statement is 1264 // not re-entrant. 1265 statement.setQuery(this); 1266 Evaluator evaluator = RolapEvaluator.create(statement); 1267 final Validator validator = createValidator(); 1268 List<ResultStyle> resultStyleList; 1269 resultStyleList = 1270 Collections.singletonList( 1271 resultStyle != null ? resultStyle : this.resultStyle); 1272 final ExpCompiler compiler = 1273 createCompiler( 1274 evaluator, validator, resultStyleList); 1275 if (scalar) { 1276 return compiler.compileScalar(exp, false); 1277 } else { 1278 return compiler.compile(exp); 1279 } 1280 } 1281 1282 public ExpCompiler createCompiler() { 1283 // REVIEW: Set query on a connection's shared internal statement is 1284 // not re-entrant. 1285 statement.setQuery(this); 1286 Evaluator evaluator = RolapEvaluator.create(statement); 1287 Validator validator = createValidator(); 1288 return createCompiler( 1289 evaluator, 1290 validator, 1291 Collections.singletonList(resultStyle)); 1292 } 1293 1294 private ExpCompiler createCompiler( 1295 final Evaluator evaluator, 1296 final Validator validator, 1297 List<ResultStyle> resultStyleList) 1298 { 1299 ExpCompiler compiler = 1300 ExpCompiler.Factory.getExpCompiler( 1301 evaluator, 1302 validator, 1303 resultStyleList); 1304 1305 final int expDeps = 1306 MondrianProperties.instance().TestExpDependencies.get(); 1307 final ProfileHandler profileHandler = statement.getProfileHandler(); 1308 if (profileHandler != null) { 1309 // Cannot test dependencies and profile at the same time. Profiling 1310 // trumps. 1311 compiler = RolapUtil.createProfilingCompiler(compiler); 1312 } else if (expDeps > 0) { 1313 compiler = RolapUtil.createDependencyTestingCompiler(compiler); 1314 } 1315 return compiler; 1316 } 1317 1318 /** 1319 * Keeps track of references to members of the measures dimension 1320 * 1321 * @param olapElement potential measure member 1322 */ 1323 public void addMeasuresMembers(OlapElement olapElement) 1324 { 1325 if (olapElement instanceof Member) { 1326 Member member = (Member) olapElement; 1327 if (member.isMeasure()) { 1328 measuresMembers.add(member); 1329 } 1330 } 1331 } 1332 1333 /** 1334 * @return set of members from the measures dimension referenced within 1335 * this query 1336 */ 1337 public Set<Member> getMeasuresMembers() { 1338 return Collections.unmodifiableSet(measuresMembers); 1339 } 1340 1341 /** 1342 * Indicates that the query cannot use native cross joins to process 1343 * this virtual cube 1344 */ 1345 public void setVirtualCubeNonNativeCrossJoin() { 1346 nativeCrossJoinVirtualCube = false; 1347 } 1348 1349 /** 1350 * @return true if the query can use native cross joins on a virtual 1351 * cube 1352 */ 1353 public boolean nativeCrossJoinVirtualCube() { 1354 return nativeCrossJoinVirtualCube; 1355 } 1356 1357 /** 1358 * Saves away the base cubes related to the virtual cube 1359 * referenced in this query 1360 * 1361 * @param baseCubes set of base cubes 1362 */ 1363 public void setBaseCubes(List<RolapCube> baseCubes) { 1364 this.baseCubes = baseCubes; 1365 } 1366 1367 /** 1368 * return the set of base cubes associated with the virtual cube referenced 1369 * in this query 1370 * 1371 * @return set of base cubes 1372 */ 1373 public List<RolapCube> getBaseCubes() { 1374 return baseCubes; 1375 } 1376 1377 public Object accept(MdxVisitor visitor) { 1378 Object o = visitor.visit(this); 1379 1380 if (visitor.shouldVisitChildren()) { 1381 // visit formulas 1382 for (Formula formula : formulas) { 1383 formula.accept(visitor); 1384 } 1385 // visit axes 1386 for (QueryAxis axis : axes) { 1387 axis.accept(visitor); 1388 } 1389 if (slicerAxis != null) { 1390 slicerAxis.accept(visitor); 1391 } 1392 } 1393 return o; 1394 } 1395 1396 /** 1397 * Put an Object value into the evaluation cache with given key. 1398 * This is used by Calc's to store information between iterations 1399 * (rather than re-generate each time). 1400 * 1401 * @param key the cache key 1402 * @param value the cache value 1403 */ 1404 public void putEvalCache(String key, Object value) { 1405 evalCache.put(key, value); 1406 } 1407 1408 /** 1409 * Gets the Object associated with the value. 1410 * 1411 * @param key the cache key 1412 * @return the cached value or null. 1413 */ 1414 public Object getEvalCache(String key) { 1415 return evalCache.get(key); 1416 } 1417 1418 /** 1419 * Remove all entries in the evaluation cache 1420 */ 1421 public void clearEvalCache() { 1422 evalCache.clear(); 1423 } 1424 1425 /** 1426 * Closes this query. 1427 * 1428 * <p>Releases any resources held. Writes statistics to log if profiling 1429 * is enabled. 1430 * 1431 * <p>This method is idempotent. 1432 * 1433 * @deprecated This method will be removed in mondrian-4.0. 1434 */ 1435 public void close() { 1436 if (ownStatement) { 1437 statement.close(); 1438 } 1439 } 1440 1441 public Statement getStatement() { 1442 return statement; 1443 } 1444 1445 /** 1446 * Sets that the query owns its statement; therefore it will need to 1447 * close it when the query is closed. 1448 * 1449 * @param ownStatement Whether the statement belongs to the query 1450 */ 1451 public void setOwnStatement(boolean ownStatement) { 1452 this.ownStatement = ownStatement; 1453 } 1454 1455 /** 1456 * Source of metadata within the scope of a query. 1457 * 1458 * <p>Note especially that {@link #getCalculatedMember(java.util.List)} 1459 * returns the calculated members defined in this query. It does not 1460 * perform access control; all calculated members defined in a query are 1461 * visible to everyone. 1462 */ 1463 private static class QuerySchemaReader 1464 extends DelegatingSchemaReader 1465 implements NameResolver.Namespace 1466 { 1467 private final Query query; 1468 1469 public QuerySchemaReader(SchemaReader cubeSchemaReader, Query query) { 1470 super(cubeSchemaReader); 1471 this.query = query; 1472 } 1473 1474 public SchemaReader withoutAccessControl() { 1475 return new QuerySchemaReader( 1476 schemaReader.withoutAccessControl(), query); 1477 } 1478 1479 public Member getMemberByUniqueName( 1480 List<Id.Segment> uniqueNameParts, 1481 boolean failIfNotFound, 1482 MatchType matchType) 1483 { 1484 final String uniqueName = Util.implode(uniqueNameParts); 1485 Member member = query.lookupMemberFromCache(uniqueName); 1486 if (member == null) { 1487 // Not a calculated member in the query, so go to the cube. 1488 member = schemaReader.getMemberByUniqueName( 1489 uniqueNameParts, failIfNotFound, matchType); 1490 } 1491 if (!failIfNotFound && member == null) { 1492 return null; 1493 } 1494 if (getRole().canAccess(member)) { 1495 return member; 1496 } else { 1497 return null; 1498 } 1499 } 1500 1501 public List<Member> getLevelMembers( 1502 Level level, 1503 boolean includeCalculated) 1504 { 1505 List<Member> members = super.getLevelMembers(level, false); 1506 if (includeCalculated) { 1507 members = Util.addLevelCalculatedMembers(this, level, members); 1508 } 1509 return members; 1510 } 1511 1512 public Member getCalculatedMember(List<Id.Segment> nameParts) { 1513 for (final Formula formula : query.formulas) { 1514 if (!formula.isMember()) { 1515 continue; 1516 } 1517 Member member = (Member) formula.getElement(); 1518 if (member == null) { 1519 continue; 1520 } 1521 if (!match(member, nameParts)) { 1522 continue; 1523 } 1524 if (!query.getConnection().getRole().canAccess(member)) { 1525 continue; 1526 } 1527 return member; 1528 } 1529 return null; 1530 } 1531 1532 private static boolean match( 1533 Member member, List<Id.Segment> nameParts) 1534 { 1535 if (Util.equalName(Util.implode(nameParts), 1536 member.getUniqueName())) 1537 { 1538 // exact match 1539 return true; 1540 } 1541 Id.Segment segment = nameParts.get(nameParts.size() - 1); 1542 while (member.getParentMember() != null) { 1543 if (!segment.matches(member.getName())) { 1544 return false; 1545 } 1546 member = member.getParentMember(); 1547 nameParts = nameParts.subList(0, nameParts.size() - 1); 1548 segment = nameParts.get(nameParts.size() - 1); 1549 } 1550 if (segment.matches(member.getName())) { 1551 return Util.equalName( 1552 member.getHierarchy().getUniqueName(), 1553 Util.implode(nameParts.subList(0, nameParts.size() - 1))); 1554 } else if (member.isAll()) { 1555 return Util.equalName( 1556 member.getHierarchy().getUniqueName(), 1557 Util.implode(nameParts)); 1558 } else { 1559 return false; 1560 } 1561 } 1562 1563 public List<Member> getCalculatedMembers(Hierarchy hierarchy) { 1564 List<Member> result = new ArrayList<Member>(); 1565 // Add calculated members in the cube. 1566 final List<Member> calculatedMembers = 1567 super.getCalculatedMembers(hierarchy); 1568 result.addAll(calculatedMembers); 1569 // Add calculated members defined in the query. 1570 for (Member member : query.getDefinedMembers()) { 1571 if (member.getHierarchy().equals(hierarchy)) { 1572 result.add(member); 1573 } 1574 } 1575 return result; 1576 } 1577 1578 public List<Member> getCalculatedMembers(Level level) { 1579 List<Member> hierarchyMembers = 1580 getCalculatedMembers(level.getHierarchy()); 1581 List<Member> result = new ArrayList<Member>(); 1582 for (Member member : hierarchyMembers) { 1583 if (member.getLevel().equals(level)) { 1584 result.add(member); 1585 } 1586 } 1587 return result; 1588 } 1589 1590 public List<Member> getCalculatedMembers() { 1591 return query.getDefinedMembers(); 1592 } 1593 1594 public OlapElement getElementChild(OlapElement parent, Id.Segment s) 1595 { 1596 return getElementChild(parent, s, MatchType.EXACT); 1597 } 1598 1599 public OlapElement getElementChild( 1600 OlapElement parent, 1601 Id.Segment s, 1602 MatchType matchType) 1603 { 1604 // first look in cube 1605 OlapElement mdxElement = 1606 schemaReader.getElementChild(parent, s, matchType); 1607 if (mdxElement != null) { 1608 return mdxElement; 1609 } 1610 // then look in defined members (fixes MONDRIAN-77) 1611 1612 // then in defined sets 1613 if (!(s instanceof Id.NameSegment)) { 1614 return null; 1615 } 1616 String name = ((Id.NameSegment) s).getName(); 1617 for (Formula formula : query.formulas) { 1618 if (formula.isMember()) { 1619 continue; // have already done these 1620 } 1621 Id id = formula.getIdentifier(); 1622 if (id.getSegments().size() == 1 1623 && id.getSegments().get(0).matches(name)) 1624 { 1625 return formula.getNamedSet(); 1626 } 1627 } 1628 1629 return mdxElement; 1630 } 1631 1632 @Override 1633 public OlapElement lookupCompoundInternal( 1634 OlapElement parent, 1635 List<Id.Segment> names, 1636 boolean failIfNotFound, 1637 int category, 1638 MatchType matchType) 1639 { 1640 if (matchType == MatchType.EXACT) { 1641 OlapElement oe = lookupCompound( 1642 parent, names, failIfNotFound, category, 1643 MatchType.EXACT_SCHEMA); 1644 if (oe != null) { 1645 return oe; 1646 } 1647 } 1648 // First look to ourselves. 1649 switch (category) { 1650 case Category.Unknown: 1651 case Category.Member: 1652 if (parent == query.cube) { 1653 final Member calculatedMember = getCalculatedMember(names); 1654 if (calculatedMember != null) { 1655 return calculatedMember; 1656 } 1657 } 1658 } 1659 switch (category) { 1660 case Category.Unknown: 1661 case Category.Set: 1662 if (parent == query.cube) { 1663 final NamedSet namedSet = getNamedSet(names); 1664 if (namedSet != null) { 1665 return namedSet; 1666 } 1667 } 1668 } 1669 // Then delegate to the next reader. 1670 OlapElement olapElement = super.lookupCompoundInternal( 1671 parent, names, failIfNotFound, category, matchType); 1672 if (olapElement instanceof Member) { 1673 Member member = (Member) olapElement; 1674 final Formula formula = (Formula) 1675 member.getPropertyValue(Property.FORMULA.name); 1676 if (formula != null) { 1677 // This is a calculated member defined against the cube. 1678 // Create a free-standing formula using the same 1679 // expression, then use the member defined in that formula. 1680 final Formula formulaClone = (Formula) formula.clone(); 1681 formulaClone.createElement(query); 1682 formulaClone.accept(query.createValidator()); 1683 olapElement = formulaClone.getMdxMember(); 1684 } 1685 } 1686 return olapElement; 1687 } 1688 1689 public NamedSet getNamedSet(List<Id.Segment> nameParts) { 1690 if (nameParts.size() != 1) { 1691 return null; 1692 } 1693 return query.lookupNamedSet(nameParts.get(0)); 1694 } 1695 1696 public Parameter getParameter(String name) { 1697 // Look for a parameter defined in the query. 1698 for (Parameter parameter : query.parameters) { 1699 if (parameter.getName().equals(name)) { 1700 return parameter; 1701 } 1702 } 1703 1704 // Look for a parameter defined in this statement. 1705 if (Util.lookup(RolapConnectionProperties.class, name) != null) { 1706 Object value = query.statement.getProperty(name); 1707 // TODO: Don't assume it's a string. 1708 // TODO: Create expression which will get the value from the 1709 // statement at the time the query is executed. 1710 Literal defaultValue = 1711 Literal.createString(String.valueOf(value)); 1712 return new ConnectionParameterImpl(name, defaultValue); 1713 } 1714 1715 return super.getParameter(name); 1716 } 1717 1718 public OlapElement lookupChild( 1719 OlapElement parent, 1720 IdentifierSegment segment, 1721 MatchType matchType) 1722 { 1723 // ignore matchType 1724 return lookupChild(parent, segment); 1725 } 1726 1727 public OlapElement lookupChild( 1728 OlapElement parent, 1729 IdentifierSegment segment) 1730 { 1731 // Only look for calculated members and named sets defined in the 1732 // query. 1733 for (Formula formula : query.getFormulas()) { 1734 if (NameResolver.matches(formula, parent, segment)) { 1735 return formula.getElement(); 1736 } 1737 } 1738 return null; 1739 } 1740 1741 public List<NameResolver.Namespace> getNamespaces() { 1742 final List<NameResolver.Namespace> list = 1743 new ArrayList<NameResolver.Namespace>(); 1744 list.add(this); 1745 list.addAll(super.getNamespaces()); 1746 return list; 1747 } 1748 } 1749 1750 private static class ConnectionParameterImpl 1751 extends ParameterImpl 1752 { 1753 public ConnectionParameterImpl(String name, Literal defaultValue) { 1754 super(name, defaultValue, "Connection property", new StringType()); 1755 } 1756 1757 public Scope getScope() { 1758 return Scope.Connection; 1759 } 1760 1761 public void setValue(Object value) { 1762 throw MondrianResource.instance().ParameterIsNotModifiable.ex( 1763 getName(), getScope().name()); 1764 } 1765 } 1766 1767 /** 1768 * Implementation of {@link mondrian.olap.Validator} that works within a 1769 * particular query. 1770 * 1771 * <p>It's unlikely that we would want a validator that is 1772 * NOT within a particular query, but by organizing the code this way, with 1773 * the majority of the code in {@link mondrian.olap.ValidatorImpl}, the 1774 * dependencies between Validator and Query are explicit. 1775 */ 1776 private static class QueryValidator extends ValidatorImpl { 1777 private final boolean alwaysResolveFunDef; 1778 private Query query; 1779 private final SchemaReader schemaReader; 1780 1781 /** 1782 * Creates a QueryValidator. 1783 * 1784 * @param functionTable Function table 1785 * @param alwaysResolveFunDef Whether to always resolve function 1786 * definitions (see {@link #alwaysResolveFunDef()}) 1787 * @param query Query 1788 */ 1789 public QueryValidator( 1790 FunTable functionTable, boolean alwaysResolveFunDef, Query query) 1791 { 1792 super(functionTable); 1793 this.alwaysResolveFunDef = alwaysResolveFunDef; 1794 this.query = query; 1795 this.schemaReader = new ScopedSchemaReader(this, true); 1796 } 1797 1798 public SchemaReader getSchemaReader() { 1799 return schemaReader; 1800 } 1801 1802 protected void defineParameter(Parameter param) { 1803 final String name = param.getName(); 1804 query.parameters.add(param); 1805 query.parametersByName.put(name, param); 1806 } 1807 1808 public Query getQuery() { 1809 return query; 1810 } 1811 1812 public boolean alwaysResolveFunDef() { 1813 return alwaysResolveFunDef; 1814 } 1815 1816 public ArrayStack<QueryPart> getScopeStack() { 1817 return stack; 1818 } 1819 } 1820 1821 /** 1822 * Schema reader that depends on the current scope during the validation 1823 * of a query. Depending on the scope, different calculated sets may be 1824 * visible. The scope is represented by the expression stack inside the 1825 * validator. 1826 */ 1827 private static class ScopedSchemaReader 1828 extends DelegatingSchemaReader 1829 implements NameResolver.Namespace 1830 { 1831 private final QueryValidator queryValidator; 1832 private final boolean accessControlled; 1833 1834 /** 1835 * Creates a ScopedSchemaReader. 1836 * 1837 * @param queryValidator Validator that is being used to validate the 1838 * query 1839 * @param accessControlled Access controlled 1840 */ 1841 private ScopedSchemaReader( 1842 QueryValidator queryValidator, 1843 boolean accessControlled) 1844 { 1845 super(queryValidator.getQuery().getSchemaReader(accessControlled)); 1846 this.queryValidator = queryValidator; 1847 this.accessControlled = accessControlled; 1848 } 1849 1850 public SchemaReader withoutAccessControl() { 1851 if (!accessControlled) { 1852 return this; 1853 } 1854 return new ScopedSchemaReader(queryValidator, false); 1855 } 1856 1857 public List<NameResolver.Namespace> getNamespaces() { 1858 final List<NameResolver.Namespace> list = 1859 new ArrayList<NameResolver.Namespace>(); 1860 list.add(this); 1861 list.addAll(super.getNamespaces()); 1862 return list; 1863 } 1864 1865 @Override 1866 public OlapElement lookupCompoundInternal( 1867 OlapElement parent, 1868 final List<Id.Segment> names, 1869 boolean failIfNotFound, 1870 int category, 1871 MatchType matchType) 1872 { 1873 switch (category) { 1874 case Category.Set: 1875 case Category.Unknown: 1876 final ScopedNamedSet namedSet = 1877 queryValidator.getQuery().lookupScopedNamedSet( 1878 names, queryValidator.getScopeStack()); 1879 if (namedSet != null) { 1880 return namedSet; 1881 } 1882 } 1883 return super.lookupCompoundInternal( 1884 parent, names, failIfNotFound, category, matchType); 1885 } 1886 1887 public OlapElement lookupChild( 1888 OlapElement parent, 1889 IdentifierSegment segment, 1890 MatchType matchType) 1891 { 1892 // ignore matchType 1893 return lookupChild(parent, segment); 1894 } 1895 1896 public OlapElement lookupChild( 1897 OlapElement parent, 1898 IdentifierSegment segment) 1899 { 1900 if (!(parent instanceof Cube)) { 1901 return null; 1902 } 1903 return queryValidator.getQuery().lookupScopedNamedSet( 1904 Collections.singletonList(Util.convert(segment)), 1905 queryValidator.getScopeStack()); 1906 } 1907 } 1908 1909 public static class ScopedNamedSet implements NamedSet { 1910 private final String name; 1911 private final QueryPart scope; 1912 private Exp expr; 1913 1914 /** 1915 * Creates a ScopedNamedSet. 1916 * 1917 * @param name Name 1918 * @param scope Scope of named set (the function call that encloses 1919 * the 'expr AS name', often GENERATE or FILTER) 1920 * @param expr Expression that defines the set 1921 */ 1922 private ScopedNamedSet(String name, QueryPart scope, Exp expr) { 1923 this.name = name; 1924 this.scope = scope; 1925 this.expr = expr; 1926 } 1927 1928 public String getName() { 1929 return name; 1930 } 1931 1932 public String getNameUniqueWithinQuery() { 1933 return System.identityHashCode(this) + ""; 1934 } 1935 1936 public boolean isDynamic() { 1937 return true; 1938 } 1939 1940 public Exp getExp() { 1941 return expr; 1942 } 1943 1944 public void setExp(Exp expr) { 1945 this.expr = expr; 1946 } 1947 1948 public void setName(String newName) { 1949 throw new UnsupportedOperationException(); 1950 } 1951 1952 public Type getType() { 1953 return expr.getType(); 1954 } 1955 1956 public Map<String, Annotation> getAnnotationMap() { 1957 return Collections.emptyMap(); 1958 } 1959 1960 public NamedSet validate(Validator validator) { 1961 Exp newExpr = expr.accept(validator); 1962 final Type type = newExpr.getType(); 1963 if (type instanceof MemberType 1964 || type instanceof TupleType) 1965 { 1966 newExpr = 1967 new UnresolvedFunCall( 1968 "{}", Syntax.Braces, new Exp[] {newExpr}) 1969 .accept(validator); 1970 } 1971 this.expr = newExpr; 1972 return this; 1973 } 1974 1975 public String getUniqueName() { 1976 return name; 1977 } 1978 1979 public String getDescription() { 1980 throw new UnsupportedOperationException(); 1981 } 1982 1983 public OlapElement lookupChild( 1984 SchemaReader schemaReader, Id.Segment s, MatchType matchType) 1985 { 1986 throw new UnsupportedOperationException(); 1987 } 1988 1989 public String getQualifiedName() { 1990 throw new UnsupportedOperationException(); 1991 } 1992 1993 public String getCaption() { 1994 throw new UnsupportedOperationException(); 1995 } 1996 1997 public boolean isVisible() { 1998 throw new UnsupportedOperationException(); 1999 } 2000 2001 public Hierarchy getHierarchy() { 2002 throw new UnsupportedOperationException(); 2003 } 2004 2005 public Dimension getDimension() { 2006 throw new UnsupportedOperationException(); 2007 } 2008 2009 public String getLocalized(LocalizedProperty prop, Locale locale) { 2010 throw new UnsupportedOperationException(); 2011 } 2012 } 2013 2014 /** 2015 * Visitor that locates and registers parameters. 2016 */ 2017 private class ParameterFinder extends MdxVisitorImpl { 2018 public Object visit(ParameterExpr parameterExpr) { 2019 Parameter parameter = parameterExpr.getParameter(); 2020 if (!parameters.contains(parameter)) { 2021 parameters.add(parameter); 2022 parametersByName.put(parameter.getName(), parameter); 2023 } 2024 return null; 2025 } 2026 2027 public Object visit(UnresolvedFunCall call) { 2028 if (call.getFunName().equals("Parameter")) { 2029 // Is there already a parameter with this name? 2030 String parameterName = 2031 ParameterFunDef.getParameterName(call.getArgs()); 2032 if (parametersByName.get(parameterName) != null) { 2033 throw MondrianResource.instance() 2034 .ParameterDefinedMoreThanOnce.ex(parameterName); 2035 } 2036 2037 Type type = 2038 ParameterFunDef.getParameterType(call.getArgs()); 2039 2040 // Create a temporary parameter. We don't know its 2041 // type yet. The default of NULL is temporary. 2042 Parameter parameter = new ParameterImpl( 2043 parameterName, Literal.nullValue, null, type); 2044 parameters.add(parameter); 2045 parametersByName.put(parameterName, parameter); 2046 } 2047 return null; 2048 } 2049 } 2050 2051 /** 2052 * Visitor that locates and registers all aliased expressions 2053 * ('expr AS alias') as named sets. The resulting named sets have scope, 2054 * therefore they can only be seen and used within that scope. 2055 */ 2056 private class AliasedExpressionFinder extends MdxVisitorImpl { 2057 @Override 2058 public Object visit(QueryAxis queryAxis) { 2059 registerAlias(queryAxis, queryAxis.getSet()); 2060 return super.visit(queryAxis); 2061 } 2062 2063 public Object visit(UnresolvedFunCall call) { 2064 registerAliasArgs(call); 2065 return super.visit(call); 2066 } 2067 2068 public Object visit(ResolvedFunCall call) { 2069 registerAliasArgs(call); 2070 return super.visit(call); 2071 } 2072 2073 /** 2074 * Registers all arguments of a function that are named sets. 2075 * 2076 * @param call Function call 2077 */ 2078 private void registerAliasArgs(FunCall call) { 2079 for (Exp exp : call.getArgs()) { 2080 registerAlias((QueryPart) call, exp); 2081 } 2082 } 2083 2084 /** 2085 * Registers a named set if an expression is of the form "expr AS 2086 * alias". 2087 * 2088 * @param parent Parent node 2089 * @param exp Expression that may be an "AS" 2090 */ 2091 private void registerAlias(QueryPart parent, Exp exp) { 2092 if (exp instanceof FunCall) { 2093 FunCall call2 = (FunCall) exp; 2094 if (call2.getSyntax() == Syntax.Infix 2095 && call2.getFunName().equals("AS")) 2096 { 2097 // Scope is the function enclosing the 'AS' expression. 2098 // For example, in 2099 // Filter(Time.Children AS s, x > y) 2100 // the scope of the set 's' is the Filter function. 2101 assert call2.getArgCount() == 2; 2102 if (call2.getArg(1) instanceof Id) { 2103 final Id id = (Id) call2.getArg(1); 2104 createScopedNamedSet( 2105 ((Id.NameSegment) id.getSegments().get(0)) 2106 .getName(), 2107 parent, 2108 call2.getArg(0)); 2109 } else if (call2.getArg(1) instanceof NamedSetExpr) { 2110 NamedSetExpr set = (NamedSetExpr) call2.getArg(1); 2111 createScopedNamedSet( 2112 set.getNamedSet().getName(), 2113 parent, 2114 call2.getArg(0)); 2115 } 2116 } 2117 } 2118 } 2119 } 2120} 2121 2122// End Query.java