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;