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) 2005-2005 Julian Hyde
009// Copyright (C) 2005-2013 Pentaho
010// All Rights Reserved.
011*/
012package mondrian.rolap;
013
014import mondrian.mdx.MdxVisitorImpl;
015import mondrian.mdx.MemberExpr;
016import mondrian.olap.*;
017import mondrian.rolap.aggmatcher.AggStar;
018import mondrian.rolap.sql.*;
019
020import java.util.ArrayList;
021import java.util.List;
022import java.util.concurrent.atomic.AtomicBoolean;
023
024import javax.sql.DataSource;
025
026/**
027 * Computes a Filter(set, condition) in SQL.
028 *
029 * @author av
030 * @since Nov 21, 2005
031 */
032public class RolapNativeFilter extends RolapNativeSet {
033
034    public RolapNativeFilter() {
035        super.setEnabled(
036            MondrianProperties.instance().EnableNativeFilter.get());
037    }
038
039    static class FilterConstraint extends SetConstraint {
040        Exp filterExpr;
041
042        public FilterConstraint(
043            CrossJoinArg[] args,
044            RolapEvaluator evaluator,
045            Exp filterExpr)
046        {
047            super(args, evaluator, true);
048            this.filterExpr = filterExpr;
049        }
050
051        /**
052         * {@inheritDoc}
053         *
054         * <p>Overriding isJoinRequired() for native filters because
055         * we have to force a join to the fact table if the filter
056         * expression references a measure.
057         */
058        protected boolean isJoinRequired() {
059            // Use a visitor and check all member expressions.
060            // If any of them is a measure, we will have to
061            // force the join to the fact table. If it is something
062            // else then we don't really care. It will show up in
063            // the evaluator as a non-all member and trigger the
064            // join when we call RolapNativeSet.isJoinRequired().
065            final AtomicBoolean mustJoin = new AtomicBoolean(false);
066            filterExpr.accept(
067                new MdxVisitorImpl() {
068                    public Object visit(MemberExpr memberExpr) {
069                        if (memberExpr.getMember().isMeasure()) {
070                            mustJoin.set(true);
071                            return null;
072                        }
073                        return super.visit(memberExpr);
074                    }
075                });
076            if (mustJoin.get() || super.isJoinRequired()) {
077                return true;
078            }
079            return false;
080        }
081
082        public void addConstraint(
083            SqlQuery sqlQuery,
084            RolapCube baseCube,
085            AggStar aggStar)
086        {
087            // Use aggregate table to generate filter condition
088            RolapNativeSql sql =
089                new RolapNativeSql(
090                    sqlQuery, aggStar, getEvaluator(), args[0].getLevel());
091            String filterSql = sql.generateFilterCondition(filterExpr);
092            sqlQuery.addHaving(filterSql);
093            super.addConstraint(sqlQuery, baseCube, aggStar);
094        }
095
096        public Object getCacheKey() {
097            List<Object> key = new ArrayList<Object>();
098            key.add(super.getCacheKey());
099            // Note required to use string in order for caching to work
100            if (filterExpr != null) {
101                key.add(filterExpr.toString());
102            }
103
104            if (this.getEvaluator() instanceof RolapEvaluator) {
105                key.add(
106                    ((RolapEvaluator)this.getEvaluator())
107                    .getSlicerMembers());
108            }
109
110            return key;
111        }
112    }
113
114    protected boolean restrictMemberTypes() {
115        return true;
116    }
117
118    NativeEvaluator createEvaluator(
119        RolapEvaluator evaluator,
120        FunDef fun,
121        Exp[] args)
122    {
123        if (!isEnabled()) {
124            return null;
125        }
126        if (!FilterConstraint.isValidContext(
127                evaluator, restrictMemberTypes()))
128        {
129            return null;
130        }
131        // is this "Filter(<set>, <numeric expr>)"
132        String funName = fun.getName();
133        if (!"Filter".equalsIgnoreCase(funName)) {
134            return null;
135        }
136
137        if (args.length != 2) {
138            return null;
139        }
140
141        // extract the set expression
142        List<CrossJoinArg[]> allArgs =
143            crossJoinArgFactory().checkCrossJoinArg(evaluator, args[0]);
144
145        // checkCrossJoinArg returns a list of CrossJoinArg arrays.  The first
146        // array is the CrossJoin dimensions.  The second array, if any,
147        // contains additional constraints on the dimensions. If either the
148        // list or the first array is null, then native cross join is not
149        // feasible.
150        if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) {
151            return null;
152        }
153
154        CrossJoinArg[] cjArgs = allArgs.get(0);
155        if (isPreferInterpreter(cjArgs, false)) {
156            return null;
157        }
158
159        // extract "order by" expression
160        SchemaReader schemaReader = evaluator.getSchemaReader();
161        DataSource ds = schemaReader.getDataSource();
162
163        // generate the WHERE condition
164        // Need to generate where condition here to determine whether
165        // or not the filter condition can be created. The filter
166        // condition could change to use an aggregate table later in evaluation
167        SqlQuery sqlQuery = SqlQuery.newQuery(ds, "NativeFilter");
168        RolapNativeSql sql =
169            new RolapNativeSql(
170                sqlQuery, null, evaluator, cjArgs[0].getLevel());
171        final Exp filterExpr = args[1];
172        String filterExprStr = sql.generateFilterCondition(filterExpr);
173        if (filterExprStr == null) {
174            return null;
175        }
176
177        // Check to see if evaluator contains a calculated member that can't be
178        // expanded.  This is necessary due to the SqlConstraintsUtils.
179        // addContextConstraint()
180        // method which gets called when generating the native SQL.
181        if (SqlConstraintUtils.containsCalculatedMember(
182                evaluator.getNonAllMembers(), true))
183        {
184            return null;
185        }
186
187        LOGGER.debug("using native filter");
188
189        final int savepoint = evaluator.savepoint();
190        try {
191            overrideContext(evaluator, cjArgs, sql.getStoredMeasure());
192            // Now construct the TupleConstraint that contains both the CJ
193            // dimensions and the additional filter on them.
194            CrossJoinArg[] combinedArgs = cjArgs;
195            if (allArgs.size() == 2) {
196                CrossJoinArg[] predicateArgs = allArgs.get(1);
197                if (predicateArgs != null) {
198                    // Combined the CJ and the additional predicate args.
199                    combinedArgs =
200                        Util.appendArrays(cjArgs, predicateArgs);
201                }
202            }
203
204            TupleConstraint constraint =
205                new FilterConstraint(combinedArgs, evaluator, filterExpr);
206            return new SetEvaluator(cjArgs, schemaReader, constraint);
207        } finally {
208            evaluator.restore(savepoint);
209        }
210    }
211}
212
213// End RolapNativeFilter.java