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