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