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    import mondrian.olap.*;
013    import mondrian.olap.fun.*;
014    import mondrian.rolap.sql.TupleConstraint;
015    
016    /**
017     * creates a {@link mondrian.olap.NativeEvaluator} that evaluates NON EMPTY
018     * CrossJoin in SQL. The generated SQL will join the dimension tables with
019     * the fact table and return all combinations that have a
020     * corresponding row in the fact table. The current context (slicer) is
021     * used for filtering (WHERE clause in SQL). This very effective computes
022     * queris like
023     * <pre>
024     *   select ...
025     *   NON EMTPY crossjoin([product].[name].members, [customer].[name].members) on rows
026     *   froms [Sales]
027     *   where ([store].[store #14])
028     * </pre>
029     * where both, customer.name and product.name have many members, but the resulting
030     * crossjoin only has few.
031     * <p>
032     * The implementation currently can not handle sets containting
033     * parent/child hierarchies, ragged hierarchies, calculated members and
034     * the ALL member. Otherwise all
035     *
036     * @author av
037     * @since Nov 21, 2005
038     */
039    public class RolapNativeCrossJoin extends RolapNativeSet {
040    
041        public RolapNativeCrossJoin() {
042            super.setEnabled(
043                MondrianProperties.instance().EnableNativeCrossJoin.get());
044        }
045    
046        /**
047         * Constraint that restricts the result to the current context.
048         *
049         * <p>If the current context contains calculated members, silently ignores
050         * them. This means means that too many members are returned, but this does
051         * not matter, because the {@link RolapConnection.NonEmptyResult} will
052         * filter out these later.</p>
053         */
054        static class NonEmptyCrossJoinConstraint extends SetConstraint {
055            NonEmptyCrossJoinConstraint(
056                CrossJoinArg[] args,
057                RolapEvaluator evaluator)
058            {
059                // Cross join ignores calculated members, including the ones from
060                // the slicer.
061                super(args, evaluator, false);
062            }
063        }
064    
065        protected boolean restrictMemberTypes() {
066            return false;
067        }
068    
069        NativeEvaluator createEvaluator(
070            RolapEvaluator evaluator,
071            FunDef fun,
072            Exp[] args)
073        {
074            if (!isEnabled()) {
075                // native crossjoins were explicitly disabled, so no need
076                // to alert about not using them
077                return null;
078            }
079            RolapCube cube = evaluator.getCube();
080    
081            CrossJoinArg[] cargs = checkCrossJoin(evaluator, fun, args);
082    
083            if (cargs == null) {
084                // Something in the arguments to the crossjoin prevented
085                // native evaluation; may need to alert
086                alertCrossJoinNonNative(
087                    evaluator,
088                    fun,
089                    "arguments not supported");
090                return null;
091            }
092    
093            // check if all CrossJoinArgs are "All" members or Calc members
094            // "All" members do not have relational expression, and Calc members
095            // in the input could produce incorrect results.
096            //
097            // If NECJ only has AllMembers, or if there is at least one CalcMember,
098            // then sql evaluation is not possible.
099            int countNonNativeInputArg = 0;
100    
101            for (CrossJoinArg arg : cargs) {
102                if (arg instanceof MemberListCrossJoinArg) {
103                    MemberListCrossJoinArg cjArg =
104                        (MemberListCrossJoinArg)arg;
105                    if (cjArg.hasAllMember() || cjArg.isEmptyCrossJoinArg()) {
106                            ++countNonNativeInputArg;
107                    }
108                    if (cjArg.hasCalcMembers()) {
109                        countNonNativeInputArg = cargs.length;
110                        break;
111                    }
112                }
113            }
114    
115            if (countNonNativeInputArg == cargs.length) {
116                // If all inputs contain "All" members; or
117                // if all inputs are MemberListCrossJoinArg with empty member list
118                // content, then native evaluation is not feasible.
119                alertCrossJoinNonNative(
120                    evaluator,
121                    fun,
122                "either all arguments contain the ALL member, " +
123                "or empty member lists, or one has a calculated member");
124                return null;
125            }
126    
127            if (isPreferInterpreter(cargs, true)) {
128                // Native evaluation wouldn't buy us anything, so no
129                // need to alert
130                return null;
131            }
132    
133            List<RolapLevel> levels = new ArrayList<RolapLevel>();
134    
135            for (int i = 0; i < cargs.length; i++) {
136                RolapLevel level = cargs[i].getLevel();
137                if (level != null) {
138                    // Only add non null levels. These levels have real
139                    // constraints.
140                    levels.add(level);
141                }
142            }
143    
144            if ((cube.isVirtual() &&
145                    !evaluator.getQuery().nativeCrossJoinVirtualCube())) {
146                // Something in the query at large (namely, some unsupported
147                // function on the [Measures] dimension) prevented native
148                // evaluation with virtual cubes; may need to alert
149                alertCrossJoinNonNative(
150                    evaluator,
151                    fun,
152                    "not all functions on [Measures] dimension supported");
153                return null;
154            }
155            if (!NonEmptyCrossJoinConstraint.isValidContext(
156                    evaluator,
157                    false,
158                    levels.toArray(new RolapLevel[levels.size()]))) {
159                // Missing join conditions due to non-conforming dimensions
160                // meant native evaluation would have led to a true cross
161                // product, which we want to defer instead of pushing it down;
162                // so no need to alert
163                return null;
164            }
165    
166            // join with fact table will always filter out those members
167            // that dont have a row in the fact table
168            if (!evaluator.isNonEmpty()) {
169                return null;
170            }
171    
172            LOGGER.debug("using native crossjoin");
173    
174            // Create a new evaluation context, eliminating any outer context for
175            // the dimensions referenced by the inputs to the NECJ
176            // (otherwise, that outer context would be incorrectly intersected
177            // with the constraints from the inputs).
178            evaluator = evaluator.push();
179    
180            Member[] evalMembers = evaluator.getMembers().clone();
181            for (RolapLevel level : levels) {
182                RolapHierarchy hierarchy = level.getHierarchy();
183                for (int i = 0; i < evalMembers.length; ++i) {
184                    Dimension evalMemberDimension =
185                        evalMembers[i].getHierarchy().getDimension();
186                    if (evalMemberDimension == hierarchy.getDimension()) {
187                        evalMembers[i] = hierarchy.getAllMember();
188                    }
189                }
190            }
191            evaluator.setContext(evalMembers);
192    
193            TupleConstraint constraint = new NonEmptyCrossJoinConstraint(cargs, evaluator);
194            SchemaReader schemaReader = evaluator.getSchemaReader();
195            return new SetEvaluator(cargs, schemaReader, constraint);
196        }
197    
198        private void alertCrossJoinNonNative(
199            RolapEvaluator evaluator,
200            FunDef fun,
201            String reason)
202        {
203            if (!(fun instanceof NonEmptyCrossJoinFunDef)) {
204                // Only alert for an explicit NonEmptyCrossJoin,
205                // since query authors use that to indicate that
206                // they expect it to be "wicked fast"
207                return;
208            }
209            if (!evaluator.getQuery().shouldAlertForNonNative(fun)) {
210                return;
211            }
212            RolapUtil.alertNonNative("NonEmptyCrossJoin", reason);
213        }
214    }
215    
216    // End RolapNativeCrossJoin.java