001    /*
002    // This software is subject to the terms of the Common Public License
003    // Agreement, available at the following URL:
004    // http://www.opensource.org/licenses/cpl.html.
005    // Copyright (C) 2004-2005 TONBELLER AG
006    // All Rights Reserved.
007    // You must accept the terms of that agreement to use this software.
008    */
009    package mondrian.rolap;
010    
011    import java.util.*;
012    
013    import mondrian.mdx.MemberExpr;
014    import mondrian.mdx.ResolvedFunCall;
015    import mondrian.olap.*;
016    import mondrian.rolap.sql.MemberChildrenConstraint;
017    import mondrian.rolap.sql.SqlQuery;
018    import mondrian.rolap.sql.TupleConstraint;
019    import mondrian.rolap.aggmatcher.AggStar;
020    
021    /**
022     * limits the result of a Member SQL query to the current evaluation context.
023     * All Members of the current context are joined against the fact table and only
024     * those rows are returned, that have an entry in the fact table.
025     * <p>
026     * For example, if you have two dimensions, "invoice" and "time", and the current
027     * context (e.g. the slicer) contains a day from the "time" dimension, then
028     * only the invoices of that day are found. Used to optimize NON EMPTY.
029     *
030     * <p> The {@link TupleConstraint} methods may silently ignore calculated
031     * members (depends on the <code>strict</code> c'tor argument), so these may
032     * return more members than the current context restricts to. The
033     * MemberChildren methods will never accept calculated members as parents,
034     * these will cause an exception.
035     *
036     * @author av
037     * @since Nov 2, 2005
038     */
039    public class SqlContextConstraint implements MemberChildrenConstraint,
040            TupleConstraint {
041        List<Object> cacheKey;
042        private Evaluator evaluator;
043        private boolean strict;
044    
045        /**
046         * @return false if this contstraint will not work for the current context
047         */
048        public static boolean isValidContext(Evaluator context) {
049            return isValidContext(context, true, null);
050        }
051    
052        /**
053         * @param context evaluation context
054         * @param disallowVirtualCube if true, check for virtual cubes
055         * @param levels levels being referenced in the current context
056         *
057         * @return false if constraint will not work for current context
058         */
059        public static boolean isValidContext(
060            Evaluator context,
061            boolean disallowVirtualCube,
062            Level [] levels)
063        {
064            if (context == null) {
065                return false;
066            }
067            RolapCube cube = (RolapCube) context.getCube();
068            if (disallowVirtualCube) {
069                if (cube.isVirtual()) {
070                    return false;
071                }
072            }
073            if (cube.isVirtual()) {
074                Query query = context.getQuery();
075                Set<RolapCube> baseCubes = new HashSet<RolapCube>();
076                List<RolapCube> baseCubeList = new ArrayList<RolapCube>();
077                if (!findVirtualCubeBaseCubes(query, baseCubes, baseCubeList)) {
078                    return false;
079                }
080                assert levels != null;
081                // we need to make sure all the levels join with each fact table;
082                // otherwise, it doesn't make sense to do the processing
083                // natively, as you'll end up with cartesian product joins!
084                // for each rolap cube, make sure there is a base cube level
085                // equivalent
086                for (RolapCube baseCube : baseCubes) {
087                    for (Level level : levels) {
088                        if (baseCube.findBaseCubeHierarchy(
089                                (RolapHierarchy)level.getHierarchy()) == null) {
090                            return false;
091                        }
092                    }
093                }
094    
095                query.setBaseCubes(baseCubeList);
096            }
097            return true;
098        }
099    
100        /**
101         * Locates base cubes related to the measures referenced in the query.
102         *
103         * @param query query referencing the virtual cube
104         * @param baseCubes set of base cubes
105         *
106         * @return true if valid measures exist
107         */
108        private static boolean findVirtualCubeBaseCubes(
109            Query query,
110            Set<RolapCube> baseCubes,
111            List<RolapCube> baseCubeList)
112        {
113            // Gather the unique set of level-to-column maps corresponding
114            // to the underlying star/cube where the measure column
115            // originates from.
116            Set<Member> measureMembers = query.getMeasuresMembers();
117            // if no measures are explicitly referenced, just use the default
118            // measure
119            if (measureMembers.isEmpty()) {
120                Cube cube = query.getCube();
121                Dimension dimension = cube.getDimensions()[0];
122                query.addMeasuresMembers(
123                    dimension.getHierarchy().getDefaultMember());
124            }
125            for (Member member : query.getMeasuresMembers()) {
126                if (member instanceof RolapStoredMeasure) {
127                    addMeasure((RolapStoredMeasure) member, baseCubes, baseCubeList);
128                } else if (member instanceof RolapCalculatedMember) {
129                    findMeasures(member.getExpression(), baseCubes, baseCubeList);
130                }
131            }
132            if (baseCubes.isEmpty()) {
133                return false;
134            }
135    
136            return true;
137        }
138    
139        /**
140         * Adds information regarding a stored measure to maps
141         *
142         * @param measure the stored measure
143         * @param baseCubes set of base cubes
144         */
145        private static void addMeasure(
146            RolapStoredMeasure measure,
147            Set<RolapCube> baseCubes,
148            List<RolapCube> baseCubeList)
149        {
150            RolapCube baseCube = measure.getCube();
151            if (baseCubes.add(baseCube)) {
152                baseCubeList.add(baseCube);
153            }
154        }
155    
156        /**
157         * Extracts the stored measures referenced in an expression
158         *
159         * @param exp expression
160         * @param baseCubes set of base cubes
161         */
162        private static void findMeasures(
163            Exp exp,
164            Set<RolapCube> baseCubes,
165            List<RolapCube> baseCubeList)
166        {
167            if (exp instanceof MemberExpr) {
168                MemberExpr memberExpr = (MemberExpr) exp;
169                Member member = memberExpr.getMember();
170                if (member instanceof RolapStoredMeasure) {
171                    addMeasure((RolapStoredMeasure) member, baseCubes, baseCubeList);
172                } else if (member instanceof RolapCalculatedMember) {
173                    findMeasures(member.getExpression(), baseCubes, baseCubeList);
174                }
175            } else if (exp instanceof ResolvedFunCall) {
176                ResolvedFunCall funCall = (ResolvedFunCall) exp;
177                Exp [] args = funCall.getArgs();
178                for (Exp arg : args) {
179                    findMeasures(arg, baseCubes, baseCubeList);
180                }
181            }
182        }
183    
184        /**
185        * Creates a SqlContextConstraint.
186        *
187        * @param evaluator Evaluator
188        * @param strict defines the behaviour if the evaluator context
189        * contains calculated members. If true, an exception is thrown,
190        * otherwise calculated members are silently ignored. The
191        * methods {@link mondrian.rolap.sql.MemberChildrenConstraint#addMemberConstraint(mondrian.rolap.sql.SqlQuery, mondrian.rolap.RolapCube, mondrian.rolap.aggmatcher.AggStar, RolapMember)} and
192        * {@link mondrian.rolap.sql.MemberChildrenConstraint#addMemberConstraint(mondrian.rolap.sql.SqlQuery, mondrian.rolap.RolapCube, mondrian.rolap.aggmatcher.AggStar, java.util.List)} will
193        * never accept a calculated member as parent.
194        */
195        SqlContextConstraint(RolapEvaluator evaluator, boolean strict) {
196            this.evaluator = evaluator;
197            this.strict = strict;
198            cacheKey = new ArrayList<Object>();
199            cacheKey.add(getClass());
200            cacheKey.add(strict);
201            cacheKey.addAll(Arrays.asList(evaluator.getMembers()));
202    
203            // For virtual cubes, context constraint should be evaluated in the
204            // query's context, because the query might reference different base
205            // cubes.
206            //
207            // Note: we could avoid adding base cubes to the key if the evaluator
208            // contains measure members referenced in the query, rather than
209            // just the default measure for the entire virtual cube. The commented
210            // code in RolapResult() that replaces the default measure seems to
211            // do that.
212            if (((RolapCube)evaluator.getCube()).isVirtual()) {
213                cacheKey.addAll(evaluator.getQuery().getBaseCubes());
214            }
215        }
216    
217        /**
218         * Called from MemberChildren: adds <code>parent</code> to the current
219         * context and restricts the SQL resultset to that new context.
220         */
221        public void addMemberConstraint(
222            SqlQuery sqlQuery,
223            RolapCube baseCube,
224            AggStar aggStar,
225            RolapMember parent)
226        {
227            if (parent.isCalculated()) {
228                throw Util.newInternal("cannot restrict SQL to calculated member");
229            }
230            Evaluator e = evaluator.push(parent);
231            SqlConstraintUtils.addContextConstraint(sqlQuery, aggStar, e, strict);
232            SqlConstraintUtils.addMemberConstraint(
233                    sqlQuery, baseCube, aggStar, parent, true);
234        }
235    
236        /**
237         * Adds <code>parents</code> to the current
238         * context and restricts the SQL resultset to that new context.
239         */
240        public void addMemberConstraint(
241            SqlQuery sqlQuery,
242            RolapCube baseCube,
243            AggStar aggStar,
244            List<RolapMember> parents)
245        {
246            SqlConstraintUtils.addContextConstraint(
247                sqlQuery, aggStar, evaluator, strict);
248            SqlConstraintUtils.addMemberConstraint(
249                sqlQuery, baseCube, aggStar, parents, true, false);
250        }
251    
252        /**
253         * Called from LevelMembers: restricts the SQL resultset to the current
254         * context.
255         */
256        public void addConstraint(SqlQuery sqlQuery, RolapCube baseCube) {
257            SqlConstraintUtils.addContextConstraint(
258                sqlQuery, null, evaluator, strict);
259        }
260    
261        /**
262         * Returns whether a join with the fact table is required. A join is
263         * required if the context contains members from dimensions other than
264         * level. If we are interested in the members of a level or a members
265         * children then it does not make sense to join only one dimension (the one
266         * that contains the requested members) with the fact table for NON EMPTY
267         * optimization.
268         */
269        protected boolean isJoinRequired() {
270            Member[] members = evaluator.getMembers();
271            // members[0] is the Measure, so loop starts at 1
272            for (int i = 1; i < members.length; i++) {
273                if (!members[i].isAll()) {
274                    return true;
275                }
276            }
277            return false;
278        }
279    
280        public void addLevelConstraint(
281            SqlQuery sqlQuery,
282            RolapCube baseCube,
283            AggStar aggStar,
284            RolapLevel level)
285        {
286            if (!isJoinRequired()) {
287                return;
288            }
289            SqlConstraintUtils.joinLevelTableToFactTable(
290                sqlQuery, baseCube, aggStar, evaluator, (RolapCubeLevel)level);
291        }
292    
293        public MemberChildrenConstraint getMemberChildrenConstraint(RolapMember parent) {
294            return this;
295        }
296    
297        public Object getCacheKey() {
298            return cacheKey;
299        }
300    
301        public Evaluator getEvaluator() {
302            return evaluator;
303        }
304    }
305    
306    // End SqlContextConstraint.java