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