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