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-2011 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap.sql; 012 013import mondrian.olap.MondrianProperties; 014import mondrian.rolap.*; 015import mondrian.rolap.aggmatcher.AggStar; 016 017import java.util.ArrayList; 018import java.util.List; 019 020/** 021 * Represents an enumeration {member1, member2, ...}. 022 * All members must to the same level and are non-calculated. 023 */ 024public class MemberListCrossJoinArg implements CrossJoinArg { 025 private final List<RolapMember> members; 026 private final RolapLevel level; 027 private final boolean restrictMemberTypes; 028 private final boolean hasCalcMembers; 029 private final boolean hasNonCalcMembers; 030 private final boolean hasAllMember; 031 private final boolean exclude; 032 033 private MemberListCrossJoinArg( 034 RolapLevel level, 035 List<RolapMember> members, 036 boolean restrictMemberTypes, 037 boolean hasCalcMembers, 038 boolean hasNonCalcMembers, 039 boolean hasAllMember, 040 boolean exclude) 041 { 042 this.level = level; 043 this.members = members; 044 this.restrictMemberTypes = restrictMemberTypes; 045 this.hasCalcMembers = hasCalcMembers; 046 this.hasNonCalcMembers = hasNonCalcMembers; 047 this.hasAllMember = hasAllMember; 048 this.exclude = exclude; 049 } 050 051 private static boolean isArgSizeSupported( 052 RolapEvaluator evaluator, 053 int argSize) 054 { 055 boolean argSizeNotSupported = false; 056 057 // Note: arg size 0 is accepted as valid CJ argument 058 // This is used to push down the "1 = 0" predicate 059 // into the emerging CJ so that the entire CJ can 060 // be natively evaluated. 061 062 // First check that the member list will not result in a predicate 063 // longer than the underlying DB could support. 064 if (argSize > MondrianProperties.instance().MaxConstraints.get()) { 065 argSizeNotSupported = true; 066 } 067 068 return !argSizeNotSupported; 069 } 070 071 072 /** 073 * Creates an instance of {@link CrossJoinArg}, 074 * or returns null if the arguments are invalid. This method also 075 * records properties of the member list such as containing 076 * calc/non calc members, and containing the All member. 077 * 078 * <p>If restrictMemberTypes is set, then the resulting argument could 079 * contain calculated members. The newly created CrossJoinArg is marked 080 * appropriately for special handling downstream. 081 * 082 * <p>If restrictMemberTypes is false, then the resulting argument 083 * contains non-calculated members of the same level (after filtering 084 * out any null members). 085 * 086 * @param evaluator the current evaluator 087 * @param args members in the list 088 * @param restrictMemberTypes whether calculated members are allowed 089 * @param exclude Whether to exclude tuples that match the predicate 090 * @return MemberListCrossJoinArg if member list is well formed, 091 * null if not. 092 */ 093 public static CrossJoinArg create( 094 RolapEvaluator evaluator, 095 final List<RolapMember> args, 096 final boolean restrictMemberTypes, 097 boolean exclude) 098 { 099 // First check that the member list will not result in a predicate 100 // longer than the underlying DB could support. 101 if (!isArgSizeSupported(evaluator, args.size())) { 102 return null; 103 } 104 105 RolapLevel level = null; 106 RolapLevel nullLevel = null; 107 boolean hasCalcMembers = false; 108 boolean hasNonCalcMembers = false; 109 110 // Crossjoin Arg is an empty member list. 111 // This is used to push down the constant "false" condition to the 112 // native evaluator. 113 if (args.size() == 0) { 114 hasNonCalcMembers = true; 115 } 116 boolean hasAllMember = false; 117 for (RolapMember m : args) { 118 if (m.isNull()) { 119 // we're going to filter out null members anyway; 120 // don't choke on the fact that their level 121 // doesn't match that of others 122 nullLevel = m.getLevel(); 123 continue; 124 } 125 126 // If "All" member, native evaluation is not possible 127 // because "All" member does not have a corresponding 128 // relational representation. 129 // 130 // "All" member is ignored during SQL generation. 131 // The complete MDX query can be evaluated natively only 132 // if there is non all member on at least one level; 133 // otherwise the generated SQL is an empty string. 134 // See SqlTupleReader.addLevelMemberSql() 135 // 136 if (m.isAll()) { 137 hasAllMember = true; 138 } 139 140 if (m.isCalculated() && !m.isParentChildLeaf()) { 141 if (restrictMemberTypes) { 142 return null; 143 } 144 hasCalcMembers = true; 145 } else { 146 hasNonCalcMembers = true; 147 } 148 if (level == null) { 149 level = m.getLevel(); 150 } else if (!level.equals(m.getLevel())) { 151 // Members should be on the same level. 152 return null; 153 } 154 } 155 if (level == null) { 156 // all members were null; use an arbitrary one of the 157 // null levels since the SQL predicate is going to always 158 // fail anyway 159 level = nullLevel; 160 } 161 162 // level will be null for an empty CJ input that is pushed down 163 // to the native evaluator. 164 // This case is not treated as a non-native input. 165 if ((level != null) && (!level.isSimple() 166 && !supportedParentChild(level, args))) 167 { 168 return null; 169 } 170 List<RolapMember> members = new ArrayList<RolapMember>(); 171 172 for (RolapMember m : args) { 173 if (m.isNull()) { 174 // filter out null members 175 continue; 176 } 177 members.add(m); 178 } 179 180 return new MemberListCrossJoinArg( 181 level, members, restrictMemberTypes, 182 hasCalcMembers, hasNonCalcMembers, hasAllMember, exclude); 183 } 184 185 private static boolean supportedParentChild( 186 RolapLevel level, List<RolapMember> args) 187 { 188 if (level.isParentChild()) { 189 boolean allArgsLeaf = true; 190 for (RolapMember rolapMember : args) { 191 if (!rolapMember.isParentChildLeaf()) { 192 allArgsLeaf = false; 193 break; 194 } 195 } 196 return allArgsLeaf; 197 } 198 return false; 199 } 200 201 public RolapLevel getLevel() { 202 return level; 203 } 204 205 public List<RolapMember> getMembers() { 206 return members; 207 } 208 209 public boolean isPreferInterpreter(boolean joinArg) { 210 if (joinArg) { 211 // If this enumeration only contains calculated members, 212 // prefer non-native evaluation. 213 return hasCalcMembers && !hasNonCalcMembers; 214 } else { 215 // For non-join usage, always prefer non-native 216 // eval, since the members are already known. 217 return true; 218 } 219 } 220 221 public void addConstraint( 222 SqlQuery sqlQuery, 223 RolapCube baseCube, 224 AggStar aggStar) 225 { 226 SqlConstraintUtils.addMemberConstraint( 227 sqlQuery, baseCube, aggStar, 228 members, restrictMemberTypes, true, exclude); 229 } 230 231 /** 232 * Returns whether the input CJ arg is empty. 233 * 234 * <p>This is used to selectively push down empty input arg into the 235 * native evaluator. 236 * 237 * @return whether the input CJ arg is empty 238 */ 239 public boolean isEmptyCrossJoinArg() { 240 return (level == null && members.size() == 0); 241 } 242 243 public boolean hasCalcMembers() { 244 return hasCalcMembers; 245 } 246 247 public boolean hasAllMember() { 248 return hasAllMember; 249 } 250 251 public int hashCode() { 252 int c = 12; 253 for (RolapMember member : members) { 254 c = 31 * c + member.hashCode(); 255 } 256 if (restrictMemberTypes) { 257 c += 1; 258 } 259 if (exclude) { 260 c += 7; 261 } 262 return c; 263 } 264 265 public boolean equals(Object obj) { 266 if (!(obj instanceof MemberListCrossJoinArg)) { 267 return false; 268 } 269 MemberListCrossJoinArg that = (MemberListCrossJoinArg) obj; 270 if (this.restrictMemberTypes != that.restrictMemberTypes) { 271 return false; 272 } 273 if (this.exclude != that.exclude) { 274 return false; 275 } 276 for (int i = 0; i < members.size(); i++) { 277 if (this.members.get(i) != that.members.get(i)) { 278 return false; 279 } 280 } 281 return true; 282 } 283} 284 285// End MemberListCrossJoinArg.java