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