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