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 and others
010// All Rights Reserved.
011*/
012package mondrian.rolap;
013
014import mondrian.mdx.MemberExpr;
015import mondrian.olap.*;
016import mondrian.rolap.aggmatcher.AggStar;
017import mondrian.rolap.sql.*;
018import mondrian.spi.Dialect;
019
020import java.util.ArrayList;
021import java.util.List;
022
023import javax.sql.DataSource;
024
025/**
026 * Computes a TopCount in SQL.
027 *
028 * @author av
029 * @since Nov 21, 2005
030  */
031public class RolapNativeTopCount extends RolapNativeSet {
032
033    public RolapNativeTopCount() {
034        super.setEnabled(
035            MondrianProperties.instance().EnableNativeTopCount.get());
036    }
037
038    static class TopCountConstraint extends SetConstraint {
039        Exp orderByExpr;
040        boolean ascending;
041        Integer topCount;
042
043        public TopCountConstraint(
044            int count,
045            CrossJoinArg[] args, RolapEvaluator evaluator,
046            Exp orderByExpr, boolean ascending)
047        {
048            super(args, evaluator, true);
049            this.orderByExpr = orderByExpr;
050            this.ascending = ascending;
051            this.topCount = new Integer(count);
052        }
053
054        /**
055         * {@inheritDoc}
056         *
057         * <p>TopCount always needs to join the fact table because we want to
058         * evaluate the top count expression which involves a fact.
059         */
060        protected boolean isJoinRequired() {
061            return true;
062        }
063
064        public void addConstraint(
065            SqlQuery sqlQuery,
066            RolapCube baseCube,
067            AggStar aggStar)
068        {
069            if (orderByExpr != null) {
070                RolapNativeSql sql =
071                    new RolapNativeSql(
072                        sqlQuery, aggStar, getEvaluator(), null);
073                String orderBySql = sql.generateTopCountOrderBy(orderByExpr);
074                Dialect dialect = sqlQuery.getDialect();
075                boolean nullable = deduceNullability(orderByExpr);
076                if (dialect.requiresOrderByAlias()) {
077                    String alias = sqlQuery.nextColumnAlias();
078                    alias = dialect.quoteIdentifier(alias);
079                    sqlQuery.addSelect(orderBySql, null, alias);
080                    sqlQuery.addOrderBy(alias, ascending, true, nullable);
081                } else {
082                    sqlQuery.addOrderBy(orderBySql, ascending, true, nullable);
083                }
084            }
085            super.addConstraint(sqlQuery, baseCube, aggStar);
086        }
087
088        private boolean deduceNullability(Exp expr) {
089            if (!(expr instanceof MemberExpr)) {
090                return true;
091            }
092            final MemberExpr memberExpr = (MemberExpr) expr;
093            if (!(memberExpr.getMember() instanceof RolapStoredMeasure)) {
094                return true;
095            }
096            final RolapStoredMeasure measure =
097                (RolapStoredMeasure) memberExpr.getMember();
098            return measure.getAggregator() != RolapAggregator.DistinctCount;
099        }
100
101        public Object getCacheKey() {
102            List<Object> key = new ArrayList<Object>();
103            key.add(super.getCacheKey());
104            // Note: need to use string in order for caching to work
105            if (orderByExpr != null) {
106                key.add(orderByExpr.toString());
107            }
108            key.add(ascending);
109            key.add(topCount);
110
111            if (this.getEvaluator() instanceof RolapEvaluator) {
112                key.add(
113                    ((RolapEvaluator)this.getEvaluator())
114                    .getSlicerMembers());
115            }
116            return key;
117        }
118    }
119
120    protected boolean restrictMemberTypes() {
121        return true;
122    }
123
124    NativeEvaluator createEvaluator(
125        RolapEvaluator evaluator,
126        FunDef fun,
127        Exp[] args)
128    {
129        boolean ascending;
130
131        if (!isEnabled()) {
132            return null;
133        }
134        if (!TopCountConstraint.isValidContext(
135                evaluator, restrictMemberTypes()))
136        {
137            return null;
138        }
139
140        // is this "TopCount(<set>, <count>, [<numeric expr>])"
141        String funName = fun.getName();
142        if ("TopCount".equalsIgnoreCase(funName)) {
143            ascending = false;
144        } else if ("BottomCount".equalsIgnoreCase(funName)) {
145            ascending = true;
146        } else {
147            return null;
148        }
149        if (args.length < 2 || args.length > 3) {
150            return null;
151        }
152
153        // extract the set expression
154        List<CrossJoinArg[]> allArgs =
155            crossJoinArgFactory().checkCrossJoinArg(evaluator, args[0]);
156
157        // checkCrossJoinArg returns a list of CrossJoinArg arrays.  The first
158        // array is the CrossJoin dimensions.  The second array, if any,
159        // contains additional constraints on the dimensions. If either the list
160        // or the first array is null, then native cross join is not feasible.
161        if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) {
162            return null;
163        }
164
165        CrossJoinArg[] cjArgs = allArgs.get(0);
166        if (isPreferInterpreter(cjArgs, false)) {
167            return null;
168        }
169
170        // extract count
171        if (!(args[1] instanceof Literal)) {
172            return null;
173        }
174        int count = ((Literal) args[1]).getIntValue();
175
176        // extract "order by" expression
177        SchemaReader schemaReader = evaluator.getSchemaReader();
178        DataSource ds = schemaReader.getDataSource();
179
180        // generate the ORDER BY Clause
181        // Need to generate top count order by to determine whether
182        // or not it can be created. The top count
183        // could change to use an aggregate table later in evaulation
184        SqlQuery sqlQuery = SqlQuery.newQuery(ds, "NativeTopCount");
185        RolapNativeSql sql =
186            new RolapNativeSql(
187                sqlQuery, null, evaluator, null);
188        Exp orderByExpr = null;
189        if (args.length == 3) {
190            orderByExpr = args[2];
191            String orderBySQL = sql.generateTopCountOrderBy(args[2]);
192            if (orderBySQL == null) {
193                return null;
194            }
195        }
196        LOGGER.debug("using native topcount");
197        final int savepoint = evaluator.savepoint();
198        try {
199            overrideContext(evaluator, cjArgs, sql.getStoredMeasure());
200
201            CrossJoinArg[] predicateArgs = null;
202            if (allArgs.size() == 2) {
203                predicateArgs = allArgs.get(1);
204            }
205
206            CrossJoinArg[] combinedArgs;
207            if (predicateArgs != null) {
208                // Combined the CJ and the additional predicate args
209                // to form the TupleConstraint.
210                combinedArgs =
211                        Util.appendArrays(cjArgs, predicateArgs);
212            } else {
213                combinedArgs = cjArgs;
214            }
215            TupleConstraint constraint =
216                new TopCountConstraint(
217                    count, combinedArgs, evaluator, orderByExpr, ascending);
218            SetEvaluator sev =
219                new SetEvaluator(cjArgs, schemaReader, constraint);
220            sev.setMaxRows(count);
221            return sev;
222        } finally {
223            evaluator.restore(savepoint);
224        }
225    }
226}
227
228// End RolapNativeTopCount.java