001    /*
002    // $Id: //open/mondrian/src/main/mondrian/olap/Query.java#114 $
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/src/main/mondrian/olap/Query.java#114 $
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 List<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                    return member;
827                }
828            }
829            return null;
830        }
831    
832        private String getUniqueNameWithoutAll(Member member) {
833            // build unique string
834            Member parentMember = member.getParentMember();
835            if ((parentMember != null) && !parentMember.isAll()) {
836                return Util.makeFqName(
837                                getUniqueNameWithoutAll(parentMember),
838                                member.getName());
839            } else {
840                return Util.makeFqName(member.getHierarchy(), member.getName());
841            }
842        }
843    
844        /**
845         * Looks up a named set.
846         */
847        private NamedSet lookupNamedSet(String name) {
848            for (Formula formula : formulas) {
849                if (!formula.isMember() &&
850                    formula.getElement() != null &&
851                    formula.getName().equals(name)) {
852                    return (NamedSet) formula.getElement();
853                }
854            }
855            return null;
856        }
857    
858        /**
859         * Returns an array of the formulas used in this query.
860         */
861        public Formula[] getFormulas() {
862            return formulas;
863        }
864    
865        /**
866         * Returns an array of this query's axes.
867         */
868        public QueryAxis[] getAxes() {
869            return axes;
870        }
871    
872        /**
873         * Remove a formula from the query. If <code>failIfUsedInQuery</code> is
874         * true, checks and throws an error if formula is used somewhere in the
875         * query.
876         */
877        public void removeFormula(String uniqueName, boolean failIfUsedInQuery) {
878            Formula formula = findFormula(uniqueName);
879            if (failIfUsedInQuery && formula != null) {
880                OlapElement mdxElement = formula.getElement();
881                //search the query tree to see if this formula expression is used
882                //anywhere (on the axes or in another formula)
883                Walker walker = new Walker(this);
884                while (walker.hasMoreElements()) {
885                    Object queryElement = walker.nextElement();
886                    if (!queryElement.equals(mdxElement)) {
887                        continue;
888                    }
889                    // mdxElement is used in the query. lets find on on which axis
890                    // or formula
891                    String formulaType = formula.isMember()
892                        ? MondrianResource.instance().CalculatedMember.str()
893                        : MondrianResource.instance().CalculatedSet.str();
894    
895                    int i = 0;
896                    Object parent = walker.getAncestor(i);
897                    Object grandParent = walker.getAncestor(i + 1);
898                    while ((parent != null) && (grandParent != null)) {
899                        if (grandParent instanceof Query) {
900                            if (parent instanceof Axis) {
901                                throw MondrianResource.instance().
902                                    MdxCalculatedFormulaUsedOnAxis.ex(
903                                    formulaType,
904                                    uniqueName,
905                                    ((QueryAxis) parent).getAxisName());
906    
907                            } else if (parent instanceof Formula) {
908                                String parentFormulaType =
909                                    ((Formula) parent).isMember()
910                                        ? MondrianResource.instance().CalculatedMember.str()
911                                        : MondrianResource.instance().CalculatedSet.str();
912                                throw MondrianResource.instance().
913                                    MdxCalculatedFormulaUsedInFormula.ex(
914                                    formulaType, uniqueName, parentFormulaType,
915                                    ((Formula) parent).getUniqueName());
916    
917                            } else {
918                                throw MondrianResource.instance().
919                                    MdxCalculatedFormulaUsedOnSlicer.ex(
920                                    formulaType, uniqueName);
921                            }
922                        }
923                        ++i;
924                        parent = walker.getAncestor(i);
925                        grandParent = walker.getAncestor(i + 1);
926                    }
927                    throw MondrianResource.instance().
928                        MdxCalculatedFormulaUsedInQuery.ex(
929                        formulaType, uniqueName, Util.unparse(this));
930                }
931            }
932    
933            // remove formula from query
934            List<Formula> formulaList = new ArrayList<Formula>();
935            for (Formula formula1 : formulas) {
936                if (!formula1.getUniqueName().equalsIgnoreCase(uniqueName)) {
937                    formulaList.add(formula1);
938                }
939            }
940    
941            // it has been found and removed
942            this.formulas = formulaList.toArray(new Formula[0]);
943        }
944    
945        /**
946         * Returns whether a formula can safely be removed from the query. It can be
947         * removed if the member or set it defines it not used anywhere else in the
948         * query, including in another formula.
949         *
950         * @param uniqueName Unique name of the member or set defined by the formula
951         * @return whether the formula can safely be removed
952         */
953        public boolean canRemoveFormula(String uniqueName) {
954            Formula formula = findFormula(uniqueName);
955            if (formula == null) {
956                return false;
957            }
958    
959            OlapElement mdxElement = formula.getElement();
960            // Search the query tree to see if this formula expression is used
961            // anywhere (on the axes or in another formula).
962            Walker walker = new Walker(this);
963            while (walker.hasMoreElements()) {
964                Object queryElement = walker.nextElement();
965                if (queryElement instanceof MemberExpr &&
966                    ((MemberExpr) queryElement).getMember().equals(mdxElement)) {
967                    return false;
968                }
969                if (queryElement instanceof NamedSetExpr &&
970                    ((NamedSetExpr) queryElement).getNamedSet().equals(mdxElement)) {
971                    return false;
972                }
973            }
974            return true;
975        }
976    
977        /**
978         * Looks up a calculated member or set defined in this Query.
979         *
980         * @param uniqueName Unique name of calculated member or set
981         * @return formula defining calculated member, or null if not found
982         */
983        public Formula findFormula(String uniqueName) {
984            for (Formula formula : formulas) {
985                if (formula.getUniqueName().equalsIgnoreCase(uniqueName)) {
986                    return formula;
987                }
988            }
989            return null;
990        }
991    
992        /**
993         * Finds formula by name and renames it to new name.
994         */
995        public void renameFormula(String uniqueName, String newName) {
996            Formula formula = findFormula(uniqueName);
997            if (formula == null) {
998                throw MondrianResource.instance().MdxFormulaNotFound.ex(
999                    "formula", uniqueName, Util.unparse(this));
1000            }
1001            formula.rename(newName);
1002        }
1003    
1004        List<Member> getDefinedMembers() {
1005            List<Member> definedMembers = new ArrayList<Member>();
1006            for (final Formula formula : formulas) {
1007                if (formula.isMember() &&
1008                    formula.getElement() != null &&
1009                    getConnection().getRole().canAccess(formula.getElement())) {
1010                    definedMembers.add((Member) formula.getElement());
1011                }
1012            }
1013            return definedMembers;
1014        }
1015    
1016        /**
1017         * Finds axis by index and sets flag to show empty cells on that axis.
1018         */
1019        public void setAxisShowEmptyCells(int axis, boolean showEmpty) {
1020            if (axis >= axes.length) {
1021                throw MondrianResource.instance().MdxAxisShowSubtotalsNotSupported.
1022                    ex(axis);
1023            }
1024            axes[axis].setNonEmpty(!showEmpty);
1025        }
1026    
1027        /**
1028         * Returns <code>Hierarchy[]</code> used on <code>axis</code>. It calls
1029         * {@link #collectHierarchies}.
1030         */
1031        public Hierarchy[] getMdxHierarchiesOnAxis(AxisOrdinal axis) {
1032            if (axis.logicalOrdinal() >= axes.length) {
1033                throw MondrianResource.instance().MdxAxisShowSubtotalsNotSupported.
1034                    ex(axis.logicalOrdinal());
1035            }
1036            QueryAxis queryAxis = (axis == AxisOrdinal.SLICER) ?
1037                    slicerAxis :
1038                    axes[axis.logicalOrdinal()];
1039            return collectHierarchies(queryAxis.getSet());
1040        }
1041    
1042        /**
1043         * Compiles an expression, using a cached compiled expression if available.
1044         *
1045         * @param exp Expression
1046         * @param scalar Whether expression is scalar
1047         * @param resultStyle Preferred result style; if null, use query's default
1048         *     result style; ignored if expression is scalar
1049         * @return compiled expression
1050         */
1051        public Calc compileExpression(
1052            Exp exp,
1053            boolean scalar,
1054            ResultStyle resultStyle)
1055        {
1056            Evaluator evaluator = RolapEvaluator.create(this);
1057            final Validator validator = createValidator();
1058            List<ResultStyle> resultStyleList;
1059            resultStyleList =
1060                 Collections.singletonList(
1061                    resultStyle != null ? resultStyle : this.resultStyle);
1062            final ExpCompiler compiler =
1063                createCompiler(
1064                    evaluator, validator, resultStyleList);
1065            if (scalar) {
1066                return compiler.compileScalar(exp, false);
1067            } else {
1068                return compiler.compile(exp);
1069            }
1070        }
1071    
1072        public ExpCompiler createCompiler() {
1073            Evaluator evaluator = RolapEvaluator.create(this);
1074            Validator validator = createValidator();
1075            return createCompiler(
1076                evaluator,
1077                validator,
1078                Collections.singletonList(resultStyle));
1079        }
1080    
1081        private ExpCompiler createCompiler(
1082            final Evaluator evaluator,
1083            final Validator validator,
1084            List<ResultStyle> resultStyleList)
1085        {
1086            ExpCompiler compiler =
1087                ExpCompiler.Factory.getExpCompiler(
1088                    evaluator,
1089                    validator,
1090                    resultStyleList);
1091    
1092            final int expDeps =
1093                MondrianProperties.instance().TestExpDependencies.get();
1094            if (expDeps > 0) {
1095                compiler = RolapUtil.createDependencyTestingCompiler(compiler);
1096            }
1097            return compiler;
1098        }
1099    
1100        /**
1101         * Keeps track of references to members of the measures dimension
1102         *
1103         * @param olapElement potential measure member
1104         */
1105        public void addMeasuresMembers(OlapElement olapElement)
1106        {
1107            if (olapElement instanceof Member) {
1108                Member member = (Member) olapElement;
1109                if (member.getDimension().getOrdinal(getCube()) == 0) {
1110                    measuresMembers.add(member);
1111                }
1112            }
1113        }
1114    
1115        /**
1116         * @return set of members from the measures dimension referenced within
1117         * this query
1118         */
1119        public Set<Member> getMeasuresMembers() {
1120            return Collections.unmodifiableSet(measuresMembers);
1121        }
1122    
1123        /**
1124         * Indicates that the query cannot use native cross joins to process
1125         * this virtual cube
1126         */
1127        public void setVirtualCubeNonNativeCrossJoin() {
1128            nativeCrossJoinVirtualCube = false;
1129        }
1130    
1131        /**
1132         * @return true if the query can use native cross joins on a virtual
1133         * cube
1134         */
1135        public boolean nativeCrossJoinVirtualCube() {
1136            return nativeCrossJoinVirtualCube;
1137        }
1138    
1139        /**
1140         * Saves away the base cubes related to the virtual cube
1141         * referenced in this query
1142         *
1143         * @param baseCubes set of base cubes
1144         */
1145        public void setBaseCubes(List<RolapCube> baseCubes) {
1146            this.baseCubes = baseCubes;
1147        }
1148    
1149        /**
1150         * return the set of base cubes associated with the virtual cube referenced
1151         * in this query
1152         *
1153         * @return set of base cubes
1154         */
1155        public List<RolapCube> getBaseCubes() {
1156            return baseCubes;
1157        }
1158    
1159        public Object accept(MdxVisitor visitor) {
1160            Object o = visitor.visit(this);
1161    
1162            // visit formulas
1163            for (Formula formula : formulas) {
1164                formula.accept(visitor);
1165            }
1166            // visit axes
1167            for (QueryAxis axis : axes) {
1168                axis.accept(visitor);
1169            }
1170            if (slicerAxis != null) {
1171                slicerAxis.accept(visitor);
1172            }
1173    
1174            return o;
1175        }
1176    
1177        /**
1178         * Put an Object value into the evaluation cache with given key.
1179         * This is used by Calc's to store information between iterations
1180         * (rather than re-generate each time).
1181         *
1182         * @param key the cache key
1183         * @param value the cache value
1184         */
1185        public void putEvalCache(String key, Object value) {
1186            evalCache.put(key, value);
1187        }
1188    
1189        /**
1190         * Gets the Object associated with the value.
1191         *
1192         * @param key the cache key
1193         * @return the cached value or null.
1194         */
1195        public Object getEvalCache(String key) {
1196            return evalCache.get(key);
1197        }
1198    
1199        /**
1200         * Remove all entries in the evaluation cache
1201         */
1202        public void clearEvalCache() {
1203            evalCache.clear();
1204        }
1205    
1206        /**
1207         * Default implementation of {@link Validator}.
1208         *
1209         * <p>Uses a stack to help us guess the type of our parent expression
1210         * before we've completely resolved our children -- necessary,
1211         * unfortunately, when figuring out whether the "*" operator denotes
1212         * multiplication or crossjoin.
1213         *
1214         * <p>Keeps track of which nodes have already been resolved, so we don't
1215         * try to resolve nodes which have already been resolved. (That would not
1216         * be wrong, but can cause resolution to be an <code>O(2^N)</code>
1217         * operation.)
1218         */
1219        private class StackValidator implements Validator {
1220            private final Stack<QueryPart> stack = new Stack<QueryPart>();
1221            private final FunTable funTable;
1222            private final Map<QueryPart, QueryPart> resolvedNodes =
1223                new HashMap<QueryPart, QueryPart>();
1224            private final QueryPart placeHolder = Literal.zero;
1225    
1226            /**
1227             * Creates a StackValidator.
1228             *
1229             * @pre funTable != null
1230             */
1231            public StackValidator(FunTable funTable) {
1232                Util.assertPrecondition(funTable != null, "funTable != null");
1233                this.funTable = funTable;
1234            }
1235    
1236            public Query getQuery() {
1237                return Query.this;
1238            }
1239    
1240            public Exp validate(Exp exp, boolean scalar) {
1241                Exp resolved;
1242                try {
1243                    resolved = (Exp) resolvedNodes.get(exp);
1244                } catch (ClassCastException e) {
1245                    // A classcast exception will occur if there is a String
1246                    // placeholder in the map. This is an internal error -- should
1247                    // not occur for any query, valid or invalid.
1248                    throw Util.newInternal(
1249                        e,
1250                        "Infinite recursion encountered while validating '" +
1251                            Util.unparse(exp) + "'");
1252                }
1253                if (resolved == null) {
1254                    try {
1255                        stack.push((QueryPart) exp);
1256                        // To prevent recursion, put in a placeholder while we're
1257                        // resolving.
1258                        resolvedNodes.put((QueryPart) exp, placeHolder);
1259                        resolved = exp.accept(this);
1260                        Util.assertTrue(resolved != null);
1261                        resolvedNodes.put((QueryPart) exp, (QueryPart) resolved);
1262                    } finally {
1263                        stack.pop();
1264                    }
1265                }
1266    
1267                if (scalar) {
1268                    final Type type = resolved.getType();
1269                    if (!TypeUtil.canEvaluate(type)) {
1270                        String exprString = Util.unparse(resolved);
1271                        throw MondrianResource.instance().MdxMemberExpIsSet.ex(exprString);
1272                    }
1273                }
1274    
1275                return resolved;
1276            }
1277    
1278            public void validate(ParameterExpr parameterExpr) {
1279                ParameterExpr resolved =
1280                    (ParameterExpr) resolvedNodes.get(parameterExpr);
1281                if (resolved != null) {
1282                    return; // already resolved
1283                }
1284                try {
1285                    stack.push(parameterExpr);
1286                    resolvedNodes.put(parameterExpr, placeHolder);
1287                    resolved = (ParameterExpr) parameterExpr.accept(this);
1288                    assert resolved != null;
1289                    resolvedNodes.put(parameterExpr, resolved);
1290                } finally {
1291                    stack.pop();
1292                }
1293            }
1294    
1295            public void validate(MemberProperty memberProperty) {
1296                MemberProperty resolved =
1297                        (MemberProperty) resolvedNodes.get(memberProperty);
1298                if (resolved != null) {
1299                    return; // already resolved
1300                }
1301                try {
1302                    stack.push(memberProperty);
1303                    resolvedNodes.put(memberProperty, placeHolder);
1304                    memberProperty.resolve(this);
1305                    resolvedNodes.put(memberProperty, memberProperty);
1306                } finally {
1307                    stack.pop();
1308                }
1309            }
1310    
1311            public void validate(QueryAxis axis) {
1312                final QueryAxis resolved = (QueryAxis) resolvedNodes.get(axis);
1313                if (resolved != null) {
1314                    return; // already resolved
1315                }
1316                try {
1317                    stack.push(axis);
1318                    resolvedNodes.put(axis, placeHolder);
1319                    axis.resolve(this);
1320                    resolvedNodes.put(axis, axis);
1321                } finally {
1322                    stack.pop();
1323                }
1324            }
1325    
1326            public void validate(Formula formula) {
1327                final Formula resolved = (Formula) resolvedNodes.get(formula);
1328                if (resolved != null) {
1329                    return; // already resolved
1330                }
1331                try {
1332                    stack.push(formula);
1333                    resolvedNodes.put(formula, placeHolder);
1334                    formula.accept(this);
1335                    resolvedNodes.put(formula, formula);
1336                } finally {
1337                    stack.pop();
1338                }
1339            }
1340    
1341            public boolean canConvert(Exp fromExp, int to, int[] conversionCount) {
1342                return TypeUtil.canConvert(
1343                    fromExp.getCategory(),
1344                    to,
1345                    conversionCount);
1346            }
1347    
1348            public boolean requiresExpression() {
1349                return requiresExpression(stack.size() - 1);
1350            }
1351    
1352            private boolean requiresExpression(int n) {
1353                if (n < 1) {
1354                    return false;
1355                }
1356                final Object parent = stack.get(n - 1);
1357                if (parent instanceof Formula) {
1358                    return ((Formula) parent).isMember();
1359                } else if (parent instanceof ResolvedFunCall) {
1360                    final ResolvedFunCall funCall = (ResolvedFunCall) parent;
1361                    if (funCall.getFunDef().getSyntax() == Syntax.Parentheses) {
1362                        return requiresExpression(n - 1);
1363                    } else {
1364                        int k = whichArg(funCall, (Exp) stack.get(n));
1365                        if (k < 0) {
1366                            // Arguments of call have mutated since call was placed
1367                            // on stack. Presumably the call has already been
1368                            // resolved correctly, so the answer we give here is
1369                            // irrelevant.
1370                            return false;
1371                        }
1372                        final FunDef funDef = funCall.getFunDef();
1373                        final int[] parameterTypes = funDef.getParameterCategories();
1374                        return parameterTypes[k] != Category.Set;
1375                    }
1376                } else if (parent instanceof UnresolvedFunCall) {
1377                    final UnresolvedFunCall funCall = (UnresolvedFunCall) parent;
1378                    if (funCall.getSyntax() == Syntax.Parentheses ||
1379                        funCall.getFunName() == "*") {
1380                        return requiresExpression(n - 1);
1381                    } else {
1382                        int k = whichArg(funCall, (Exp) stack.get(n));
1383                        if (k < 0) {
1384                            // Arguments of call have mutated since call was placed
1385                            // on stack. Presumably the call has already been
1386                            // resolved correctly, so the answer we give here is
1387                            // irrelevant.
1388                            return false;
1389                        }
1390                        return funTable.requiresExpression(funCall, k, this);
1391                    }
1392                } else {
1393                    return false;
1394                }
1395            }
1396    
1397            public FunTable getFunTable() {
1398                return funTable;
1399            }
1400    
1401            public Parameter createOrLookupParam(
1402                boolean definition,
1403                String name,
1404                Type type,
1405                Exp defaultExp,
1406                String description)
1407            {
1408                final SchemaReader schemaReader = getSchemaReader(false);
1409                Parameter param = schemaReader.getParameter(name);
1410    
1411                if (definition) {
1412                    if (param != null) {
1413                        if (param.getScope() == Parameter.Scope.Statement) {
1414                            ParameterImpl paramImpl = (ParameterImpl) param;
1415                            paramImpl.setDescription(description);
1416                            paramImpl.setDefaultExp(defaultExp);
1417                            paramImpl.setType(type);
1418                        }
1419                        return param;
1420                    }
1421                    param = new ParameterImpl(
1422                        name,
1423                        defaultExp, description, type);
1424    
1425                    // Append it to the list of known parameters.
1426                    parameters.add(param);
1427                    parametersByName.put(name, param);
1428                    return param;
1429                } else {
1430                    if (param != null) {
1431                        return param;
1432                    }
1433                    throw MondrianResource.instance().UnknownParameter.ex(name);
1434                }
1435            }
1436    
1437            private int whichArg(final FunCall node, final Exp arg) {
1438                final Exp[] children = node.getArgs();
1439                for (int i = 0; i < children.length; i++) {
1440                    if (children[i] == arg) {
1441                        return i;
1442                    }
1443                }
1444                return -1;
1445            }
1446        }
1447    
1448        /**
1449         * Source of metadata within the scope of a query.
1450         *
1451         * <p>Note especially that {@link #getCalculatedMember(java.util.List)}
1452         * returns the calculated members defined in this query.
1453         */
1454        private class QuerySchemaReader extends DelegatingSchemaReader {
1455    
1456            public QuerySchemaReader(SchemaReader cubeSchemaReader) {
1457                super(cubeSchemaReader);
1458            }
1459    
1460            public Member getMemberByUniqueName(
1461                    List<Id.Segment> uniqueNameParts,
1462                    boolean failIfNotFound,
1463                    MatchType matchType)
1464            {
1465                final String uniqueName = Util.implode(uniqueNameParts);
1466                Member member = lookupMemberFromCache(uniqueName);
1467                if (member == null) {
1468                    // Not a calculated member in the query, so go to the cube.
1469                    member = schemaReader.getMemberByUniqueName(
1470                        uniqueNameParts, failIfNotFound, matchType);
1471                }
1472                if (!failIfNotFound && member == null) {
1473                    return null;
1474                }
1475                if (getRole().canAccess(member)) {
1476                    return member;
1477                } else {
1478                    return null;
1479                }
1480            }
1481    
1482            public List<Member> getLevelMembers(
1483                Level level,
1484                boolean includeCalculated)
1485            {
1486                List<Member> members = super.getLevelMembers(level, false);
1487                if (includeCalculated) {
1488                    members = Util.addLevelCalculatedMembers(this, level, members);
1489                }
1490                return members;
1491            }
1492    
1493            public Member getCalculatedMember(List<Id.Segment> nameParts) {
1494                final String uniqueName = Util.implode(nameParts);
1495                return lookupMemberFromCache(uniqueName);
1496            }
1497    
1498            public List<Member> getCalculatedMembers(Hierarchy hierarchy) {
1499                List<Member> result = new ArrayList<Member>();
1500                // Add calculated members in the cube.
1501                final List<Member> calculatedMembers =
1502                    super.getCalculatedMembers(hierarchy);
1503                result.addAll(calculatedMembers);
1504                // Add calculated members defined in the query.
1505                for (Member member : getDefinedMembers()) {
1506                    if (member.getHierarchy().equals(hierarchy)) {
1507                        result.add(member);
1508                    }
1509                }
1510                return result;
1511            }
1512    
1513            public List<Member> getCalculatedMembers(Level level) {
1514                List<Member> hierarchyMembers =
1515                    getCalculatedMembers(level.getHierarchy());
1516                List<Member> result = new ArrayList<Member>();
1517                for (Member member : hierarchyMembers) {
1518                    if (member.getLevel().equals(level)) {
1519                        result.add(member);
1520                    }
1521                }
1522                return result;
1523            }
1524    
1525            public List<Member> getCalculatedMembers() {
1526                return getDefinedMembers();
1527            }
1528    
1529            public OlapElement getElementChild(OlapElement parent, Id.Segment s)
1530            {
1531                return getElementChild(parent, s, MatchType.EXACT);
1532            }
1533    
1534            public OlapElement getElementChild(
1535                OlapElement parent, Id.Segment s, MatchType matchType)
1536            {
1537                // first look in cube
1538                OlapElement mdxElement =
1539                    schemaReader.getElementChild(parent, s, matchType);
1540                if (mdxElement != null) {
1541                    return mdxElement;
1542                }
1543                // then look in defined members (removed sf#1084651)
1544    
1545                // then in defined sets
1546                for (Formula formula : formulas) {
1547                    if (formula.isMember()) {
1548                        continue;       // have already done these
1549                    }
1550                    Id id = formula.getIdentifier();
1551                    if (id.getSegments().size() == 1 &&
1552                        id.getSegments().get(0).matches(s.name)) {
1553                        return formula.getNamedSet();
1554                    }
1555                }
1556    
1557                return mdxElement;
1558            }
1559    
1560            public OlapElement lookupCompound(
1561                OlapElement parent,
1562                List<Id.Segment> names,
1563                boolean failIfNotFound,
1564                int category)
1565            {
1566                return lookupCompound(
1567                    parent, names, failIfNotFound, category, MatchType.EXACT);
1568            }
1569    
1570            public OlapElement lookupCompound(
1571                    OlapElement parent,
1572                    List<Id.Segment> names,
1573                    boolean failIfNotFound,
1574                    int category,
1575                    MatchType matchType)
1576            {
1577                // First look to ourselves.
1578                switch (category) {
1579                case Category.Unknown:
1580                case Category.Member:
1581                    if (parent == cube) {
1582                        final Member calculatedMember = getCalculatedMember(names);
1583                        if (calculatedMember != null) {
1584                            return calculatedMember;
1585                        }
1586                    }
1587                }
1588                switch (category) {
1589                case Category.Unknown:
1590                case Category.Set:
1591                    if (parent == cube) {
1592                        final NamedSet namedSet = getNamedSet(names);
1593                        if (namedSet != null) {
1594                            return namedSet;
1595                        }
1596                    }
1597                }
1598                // Then delegate to the next reader.
1599                OlapElement olapElement = super.lookupCompound(
1600                        parent, names, failIfNotFound, category, matchType);
1601                if (olapElement instanceof Member) {
1602                    Member member = (Member) olapElement;
1603                    final Formula formula = (Formula)
1604                        member.getPropertyValue(Property.FORMULA.name);
1605                    if (formula != null) {
1606                        // This is a calculated member defined against the cube.
1607                        // Create a free-standing formula using the same
1608                        // expression, then use the member defined in that formula.
1609                        final Formula formulaClone = (Formula) formula.clone();
1610                        formulaClone.createElement(Query.this);
1611                        formulaClone.accept(createValidator());
1612                        olapElement = formulaClone.getMdxMember();
1613                    }
1614                }
1615                return olapElement;
1616            }
1617    
1618            public NamedSet getNamedSet(List<Id.Segment> nameParts) {
1619                if (nameParts.size() != 1) {
1620                    return null;
1621                }
1622                return lookupNamedSet(nameParts.get(0).name);
1623            }
1624    
1625            public Parameter getParameter(String name) {
1626                // Look for a parameter defined in the query.
1627                for (Parameter parameter : parameters) {
1628                    if (parameter.getName().equals(name)) {
1629                        return parameter;
1630                    }
1631                }
1632    
1633                // Look for a parameter defined in this connection.
1634                if (Util.lookup(RolapConnectionProperties.class, name) != null) {
1635                    Object value = connection.getProperty(name);
1636                    // TODO: Don't assume it's a string.
1637                    // TODO: Create expression which will get the value from the
1638                    //  connection at the time the query is executed.
1639                    Literal defaultValue =
1640                        Literal.createString(String.valueOf(value));
1641                    return new ConnectionParameterImpl(name, defaultValue);
1642                }
1643    
1644                return super.getParameter(name);
1645            }
1646    
1647        }
1648    
1649        private static class ConnectionParameterImpl
1650            extends ParameterImpl
1651        {
1652            public ConnectionParameterImpl(String name, Literal defaultValue) {
1653                super(name, defaultValue, "Connection property", new StringType());
1654            }
1655    
1656            public Scope getScope() {
1657                return Scope.Connection;
1658            }
1659    
1660            public void setValue(Object value) {
1661                throw MondrianResource.instance().ParameterIsNotModifiable.ex(
1662                    getName(), getScope().name());
1663            }
1664        }
1665    }
1666    
1667    // End Query.java