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