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