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) 2004-2005 TONBELLER AG
008// Copyright (C) 2006-2013 Pentaho
009// All Rights Reserved.
010*/
011package mondrian.rolap;
012
013import mondrian.olap.*;
014import mondrian.olap.fun.NonEmptyCrossJoinFunDef;
015import mondrian.rolap.sql.*;
016
017import java.util.*;
018
019/**
020 * Creates a {@link mondrian.olap.NativeEvaluator} that evaluates NON EMPTY
021 * CrossJoin in SQL. The generated SQL will join the dimension tables with
022 * the fact table and return all combinations that have a
023 * corresponding row in the fact table. The current context (slicer) is
024 * used for filtering (WHERE clause in SQL). This very effective computes
025 * queries like
026 *
027 * <pre>
028 *   SELECT ...
029 *   NON EMTPY Crossjoin(
030 *       [product].[name].members,
031 *       [customer].[name].members) ON ROWS
032 *   FROM [Sales]
033 *   WHERE ([store].[store #14])
034 * </pre>
035 *
036 * where both, customer.name and product.name have many members, but the
037 * resulting crossjoin only has few.
038 *
039 * <p>The implementation currently can not handle sets containting
040 * parent/child hierarchies, ragged hierarchies, calculated members and
041 * the ALL member. Otherwise all
042 *
043 * @author av
044 * @since Nov 21, 2005
045 */
046public class RolapNativeCrossJoin extends RolapNativeSet {
047
048    public RolapNativeCrossJoin() {
049        super.setEnabled(
050            MondrianProperties.instance().EnableNativeCrossJoin.get());
051    }
052
053    /**
054     * Constraint that restricts the result to the current context.
055     *
056     * <p>If the current context contains calculated members, silently ignores
057     * them. This means means that too many members are returned, but this does
058     * not matter, because the {@link RolapConnection.NonEmptyResult} will
059     * filter out these later.</p>
060     */
061    static class NonEmptyCrossJoinConstraint extends SetConstraint {
062        NonEmptyCrossJoinConstraint(
063            CrossJoinArg[] args,
064            RolapEvaluator evaluator)
065        {
066            // Cross join ignores calculated members, including the ones from
067            // the slicer.
068            super(args, evaluator, false);
069        }
070
071        public RolapMember findMember(Object key) {
072            for (CrossJoinArg arg : args) {
073                if (arg instanceof MemberListCrossJoinArg) {
074                    final MemberListCrossJoinArg crossJoinArg =
075                        (MemberListCrossJoinArg) arg;
076                    final List<RolapMember> memberList =
077                        crossJoinArg.getMembers();
078                    for (RolapMember rolapMember : memberList) {
079                        if (key.equals(rolapMember.getKey())) {
080                            return rolapMember;
081                        }
082                    }
083                }
084            }
085            return null;
086        }
087    }
088
089    protected boolean restrictMemberTypes() {
090        return false;
091    }
092
093    NativeEvaluator createEvaluator(
094        RolapEvaluator evaluator,
095        FunDef fun,
096        Exp[] args)
097    {
098        if (!isEnabled()) {
099            // native crossjoins were explicitly disabled, so no need
100            // to alert about not using them
101            return null;
102        }
103        RolapCube cube = evaluator.getCube();
104
105        List<CrossJoinArg[]> allArgs =
106            crossJoinArgFactory()
107                .checkCrossJoin(evaluator, fun, args, false);
108
109        // checkCrossJoinArg returns a list of CrossJoinArg arrays.  The first
110        // array is the CrossJoin dimensions.  The second array, if any,
111        // contains additional constraints on the dimensions. If either the list
112        // or the first array is null, then native cross join is not feasible.
113        if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) {
114            // Something in the arguments to the crossjoin prevented
115            // native evaluation; may need to alert
116            alertCrossJoinNonNative(
117                evaluator,
118                fun,
119                "arguments not supported");
120            return null;
121        }
122
123        CrossJoinArg[] cjArgs = allArgs.get(0);
124
125        // check if all CrossJoinArgs are "All" members or Calc members
126        // "All" members do not have relational expression, and Calc members
127        // in the input could produce incorrect results.
128        //
129        // If NECJ only has AllMembers, or if there is at least one CalcMember,
130        // then sql evaluation is not possible.
131        int countNonNativeInputArg = 0;
132
133        for (CrossJoinArg arg : cjArgs) {
134            if (arg instanceof MemberListCrossJoinArg) {
135                MemberListCrossJoinArg cjArg =
136                    (MemberListCrossJoinArg)arg;
137                if (cjArg.hasAllMember() || cjArg.isEmptyCrossJoinArg()) {
138                    ++countNonNativeInputArg;
139                }
140                if (cjArg.hasCalcMembers()) {
141                    countNonNativeInputArg = cjArgs.length;
142                    break;
143                }
144            }
145        }
146
147        if (countNonNativeInputArg == cjArgs.length) {
148            // If all inputs contain "All" members; or
149            // if all inputs are MemberListCrossJoinArg with empty member list
150            // content, then native evaluation is not feasible.
151            alertCrossJoinNonNative(
152                evaluator,
153                fun,
154                "either all arguments contain the ALL member, "
155                + "or empty member lists, or one has a calculated member");
156            return null;
157        }
158
159        if (isPreferInterpreter(cjArgs, true)) {
160            // Native evaluation wouldn't buy us anything, so no
161            // need to alert
162            return null;
163        }
164
165        // Verify that args are valid
166        List<RolapLevel> levels = new ArrayList<RolapLevel>();
167        for (CrossJoinArg cjArg : cjArgs) {
168            RolapLevel level = cjArg.getLevel();
169            if (level != null) {
170                // Only add non null levels. These levels have real
171                // constraints.
172                levels.add(level);
173            }
174        }
175
176        if (cube.isVirtual()
177            && !evaluator.getQuery().nativeCrossJoinVirtualCube())
178        {
179            // Something in the query at large (namely, some unsupported
180            // function on the [Measures] dimension) prevented native
181            // evaluation with virtual cubes; may need to alert
182            alertCrossJoinNonNative(
183                evaluator,
184                fun,
185                "not all functions on [Measures] dimension supported");
186            return null;
187        }
188
189        if (!NonEmptyCrossJoinConstraint.isValidContext(
190                evaluator,
191                false,
192                levels.toArray(new RolapLevel[levels.size()]),
193                restrictMemberTypes()))
194        {
195            // Missing join conditions due to non-conforming dimensions
196            // meant native evaluation would have led to a true cross
197            // product, which we want to defer instead of pushing it down;
198            // so no need to alert
199            return null;
200        }
201
202        // join with fact table will always filter out those members
203        // that dont have a row in the fact table
204        if (!evaluator.isNonEmpty()) {
205            return null;
206        }
207
208        LOGGER.debug("using native crossjoin");
209
210        // Create a new evaluation context, eliminating any outer context for
211        // the dimensions referenced by the inputs to the NECJ
212        // (otherwise, that outer context would be incorrectly intersected
213        // with the constraints from the inputs).
214        final int savepoint = evaluator.savepoint();
215
216        try {
217            overrideContext(evaluator, cjArgs, null);
218
219            // Use the combined CrossJoinArg for the tuple constraint,
220            // which will be translated to the SQL WHERE clause.
221            CrossJoinArg[] cargs = combineArgs(allArgs);
222
223            // Now construct the TupleConstraint that contains both the CJ
224            // dimensions and the additional filter on them. It will make a
225            // copy of the evaluator.
226            TupleConstraint constraint =
227                buildConstraint(evaluator, fun, cargs);
228
229            // Use the just the CJ CrossJoiArg for the evaluator context,
230            // which will be translated to select list in sql.
231            final SchemaReader schemaReader = evaluator.getSchemaReader();
232            return new SetEvaluator(cjArgs, schemaReader, constraint);
233        } finally {
234            evaluator.restore(savepoint);
235        }
236    }
237
238    CrossJoinArg[] combineArgs(
239        List<CrossJoinArg[]> allArgs)
240    {
241        CrossJoinArg[] cjArgs = allArgs.get(0);
242        if (allArgs.size() == 2) {
243            CrossJoinArg[] predicateArgs = allArgs.get(1);
244            if (predicateArgs != null) {
245                // Combine the CJ and the additional predicate args.
246                return Util.appendArrays(cjArgs, predicateArgs);
247            }
248        }
249        return cjArgs;
250    }
251
252    private TupleConstraint buildConstraint(
253        final RolapEvaluator evaluator,
254        final FunDef fun,
255        final CrossJoinArg[] cargs)
256    {
257        CrossJoinArg[] myArgs;
258        if (safeToConstrainByOtherAxes(fun)) {
259            myArgs = buildArgs(evaluator, cargs);
260        } else {
261            myArgs = cargs;
262        }
263        return new NonEmptyCrossJoinConstraint(myArgs, evaluator);
264    }
265
266    private CrossJoinArg[] buildArgs(
267        final RolapEvaluator evaluator, final CrossJoinArg[] cargs)
268    {
269        Set<CrossJoinArg> joinArgs =
270            crossJoinArgFactory().buildConstraintFromAllAxes(evaluator);
271        joinArgs.addAll(Arrays.asList(cargs));
272        return joinArgs.toArray(new CrossJoinArg[joinArgs.size()]);
273    }
274
275    private boolean safeToConstrainByOtherAxes(final FunDef fun) {
276        return !(fun instanceof NonEmptyCrossJoinFunDef);
277    }
278
279    private void alertCrossJoinNonNative(
280        RolapEvaluator evaluator,
281        FunDef fun,
282        String reason)
283    {
284        if (!(fun instanceof NonEmptyCrossJoinFunDef)) {
285            // Only alert for an explicit NonEmptyCrossJoin,
286            // since query authors use that to indicate that
287            // they expect it to be "wicked fast"
288            return;
289        }
290        if (!evaluator.getQuery().shouldAlertForNonNative(fun)) {
291            return;
292        }
293        RolapUtil.alertNonNative("NonEmptyCrossJoin", reason);
294    }
295}
296
297// End RolapNativeCrossJoin.java
298