001 /* 002 // $Id: //open/mondrian/src/main/mondrian/olap/fun/AggregateFunDef.java#23 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 2005-2008 Julian Hyde 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.olap.fun; 011 012 import mondrian.calc.Calc; 013 import mondrian.calc.ExpCompiler; 014 import mondrian.calc.ListCalc; 015 import mondrian.calc.impl.AbstractDoubleCalc; 016 import mondrian.calc.impl.GenericCalc; 017 import mondrian.calc.impl.ValueCalc; 018 import mondrian.mdx.ResolvedFunCall; 019 import mondrian.olap.*; 020 import mondrian.rolap.RolapAggregator; 021 import mondrian.rolap.RolapEvaluator; 022 023 import java.util.*; 024 025 import org.eigenbase.util.property.IntegerProperty; 026 027 /** 028 * Definition of the <code>AGGREGATE</code> MDX function. 029 * 030 * @author jhyde 031 * @since 2005/8/14 032 * @version $Id: //open/mondrian/src/main/mondrian/olap/fun/AggregateFunDef.java#23 $ 033 */ 034 public class AggregateFunDef extends AbstractAggregateFunDef { 035 static final ReflectiveMultiResolver resolver = 036 new ReflectiveMultiResolver( 037 "Aggregate", "Aggregate(<Set>[, <Numeric Expression>])", 038 "Returns a calculated value using the appropriate aggregate function, based on the context of the query.", 039 new String[]{"fnx", "fnxn"}, 040 AggregateFunDef.class); 041 042 public AggregateFunDef(FunDef dummyFunDef) { 043 super(dummyFunDef); 044 } 045 046 public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { 047 final ListCalc listCalc = compiler.compileList(call.getArg(0)); 048 final Calc calc = call.getArgCount() > 1 ? 049 compiler.compileScalar(call.getArg(1), true) : 050 new ValueCalc(call); 051 return new AggregateCalc(call, listCalc, calc); 052 } 053 054 public static class AggregateCalc extends AbstractDoubleCalc { 055 private final ListCalc listCalc; 056 private final Calc calc; 057 058 public AggregateCalc(Exp exp, ListCalc listCalc, Calc calc) { 059 super(exp, new Calc[]{listCalc, calc}); 060 this.listCalc = listCalc; 061 this.calc = calc; 062 } 063 064 public double evaluateDouble(Evaluator evaluator) { 065 Aggregator aggregator = 066 (Aggregator) evaluator.getProperty( 067 Property.AGGREGATION_TYPE.name, null); 068 if (aggregator == null) { 069 throw newEvalException( 070 null, 071 "Could not find an aggregator in the current evaluation context"); 072 } 073 Aggregator rollup = aggregator.getRollup(); 074 if (rollup == null) { 075 throw newEvalException( 076 null, 077 "Don't know how to rollup aggregator '" + aggregator + "'"); 078 } 079 List list = evaluateCurrentList(listCalc, evaluator); 080 if (aggregator == RolapAggregator.DistinctCount) { 081 // If the list is empty, it means the current context 082 // contains no qualifying cells. The result set is empty. 083 if (list.size() == 0) { 084 return DoubleNull; 085 } 086 087 // Optimize the list 088 // E.g. 089 // List consists of: 090 // (Gender.[All Gender], [Product].[All Products]), 091 // (Gender.[All Gender].[F], [Product].[All Products].[Drink]), 092 // (Gender.[All Gender].[M], [Product].[All Products].[Food]) 093 // Can be optimized to: 094 // (Gender.[All Gender], [Product].[All Products]) 095 // 096 // Similar optimization can also be done for list of members. 097 List<Member[]> tupleList; 098 if (list.get(0) instanceof Member) { 099 tupleList = makeTupleList((List<Member>)list); 100 } else { 101 tupleList = (List<Member[]>) list; 102 } 103 104 RolapEvaluator rolapEvaluator = null; 105 if (evaluator instanceof RolapEvaluator) { 106 rolapEvaluator = (RolapEvaluator) evaluator; 107 } 108 109 if ((rolapEvaluator != null) && 110 rolapEvaluator.getDialect().supportsUnlimitedValueList()) { 111 // If the DBMS does not have an upper limit on IN list 112 // predicate size, then don't attempt any list 113 // optimization, since the current algorithm is 114 // very slow. May want to revisit this if someone 115 // improves the algorithm. 116 } else { 117 // FIXME: We remove overlapping tuple entries only to pass 118 // AggregationOnDistinctCountMeasuresTest 119 // .testOptimizeListWithTuplesOfLength3 on Access. Without 120 // the optimization, we generate a statement 7000 121 // characters long and Access gives "Query is too complex". 122 // The optimization is expensive, so we only want to do it 123 // if the DBMS can't execute the query otherwise. 124 if ((rolapEvaluator != null) && 125 rolapEvaluator.getDialect().isAccess() && 126 false) { 127 tupleList = removeOverlappingTupleEntries(tupleList); 128 } 129 if (true) { 130 tupleList = 131 optimizeChildren( 132 tupleList, 133 evaluator.getSchemaReader(), 134 evaluator.getMeasureCube()); 135 } 136 checkIfAggregationSizeIsTooLarge(tupleList); 137 } 138 139 // Can't aggregate distinct-count values in the same way 140 // which is used for other types of aggregations. To evaluate a 141 // distinct-count across multiple members, we need to gather 142 // the members together, then evaluate the collection of 143 // members all at once. To do this, we postpone evaluation, 144 // and create a lambda function containing the members. 145 Evaluator evaluator2 = 146 evaluator.pushAggregation(tupleList); 147 final Object o = evaluator2.evaluateCurrent(); 148 final Number number = (Number) o; 149 return GenericCalc.numberToDouble(number); 150 } 151 return (Double) rollup.aggregate(evaluator.push(), list, calc); 152 } 153 154 /** 155 * In case of distinct count aggregation if a tuple which is a super 156 * set of other tuples in the set exists then the child tuples can be 157 * ignored. 158 * 159 * <p> 160 * E.g. 161 * List consists of: 162 * (Gender.[All Gender], [Product].[All Products]), 163 * (Gender.[All Gender].[F], [Product].[All Products].[Drink]), 164 * (Gender.[All Gender].[M], [Product].[All Products].[Food]) 165 * Can be optimized to: 166 * (Gender.[All Gender], [Product].[All Products]) 167 * 168 * @param list 169 */ 170 171 public static List<Member[]> removeOverlappingTupleEntries(List<Member[]> list) { 172 List<Member[]> trimmedList = new ArrayList<Member[]>(); 173 for (Member[] tuple1 : list) { 174 if (trimmedList.isEmpty()) { 175 trimmedList.add(tuple1); 176 } else { 177 boolean ignore = false; 178 final Iterator<Member[]> iterator = trimmedList.iterator(); 179 while (iterator.hasNext()) { 180 Member[] tuple2 = iterator.next(); 181 if (isSuperSet(tuple1, tuple2)) { 182 iterator.remove(); 183 } else if (isSuperSet(tuple2, tuple1) || 184 isEqual(tuple1, tuple2)) { 185 ignore = true; 186 break; 187 } 188 } 189 if (!ignore) { 190 trimmedList.add(tuple1); 191 } 192 } 193 } 194 return trimmedList; 195 } 196 197 /** 198 * Returns whether tuple1 is a superset of tuple2 199 * @param tuple1 200 * @param tuple2 201 * @return boolean 202 */ 203 public static boolean isSuperSet(Member[] tuple1, Member[] tuple2) { 204 int parentLevelCount = 0; 205 for (int i = 0; i < tuple1.length; i++) { 206 Member member1 = tuple1[i]; 207 Member member2 = tuple2[i]; 208 209 if (!member2.isChildOrEqualTo(member1)) { 210 return false; 211 } 212 if (member1.getLevel().getDepth() < member2.getLevel().getDepth()) { 213 parentLevelCount++; 214 } 215 } 216 return parentLevelCount > 0; 217 } 218 219 /** 220 * Forms a list tuples from a list of members 221 * @param list of members 222 * @return list of tuples 223 */ 224 public static List<Member[]> makeTupleList(List<Member> list) { 225 List<Member[]> tupleList = new ArrayList<Member[]>(list.size()); 226 for (Member member : list) { 227 tupleList.add(new Member[] {member}); 228 } 229 return tupleList; 230 } 231 232 private void checkIfAggregationSizeIsTooLarge(List list) { 233 final IntegerProperty property = 234 MondrianProperties.instance().MaxConstraints; 235 final int maxConstraints = property.get(); 236 if (list.size() > maxConstraints) { 237 throw newEvalException( 238 null, 239 "Distinct Count aggregation is not supported over a list" 240 + " with more than " + maxConstraints + " predicates" 241 + " (see property " + property.getPath() + ")"); 242 } 243 } 244 245 public Calc[] getCalcs() { 246 return new Calc[] {listCalc, calc}; 247 } 248 249 public boolean dependsOn(Dimension dimension) { 250 if (dimension.isMeasures()) { 251 return true; 252 } 253 return anyDependsButFirst(getCalcs(), dimension); 254 } 255 256 /** 257 * In distinct Count aggregation, if tuple list is a result 258 * m.children * n.children then it can be optimized to m * n 259 * 260 * <p> 261 * E.g. 262 * List consist of: 263 * (Gender.[All Gender].[F], [Store].[All Stores].[USA]), 264 * (Gender.[All Gender].[F], [Store].[All Stores].[USA].[OR]), 265 * (Gender.[All Gender].[F], [Store].[All Stores].[USA].[CA]), 266 * (Gender.[All Gender].[F], [Store].[All Stores].[USA].[WA]), 267 * (Gender.[All Gender].[F], [Store].[All Stores].[CANADA]) 268 * (Gender.[All Gender].[M], [Store].[All Stores].[USA]), 269 * (Gender.[All Gender].[M], [Store].[All Stores].[USA].[OR]), 270 * (Gender.[All Gender].[M], [Store].[All Stores].[USA].[CA]), 271 * (Gender.[All Gender].[M], [Store].[All Stores].[USA].[WA]), 272 * (Gender.[All Gender].[M], [Store].[All Stores].[CANADA]) 273 * Can be optimized to: 274 * (Gender.[All Gender], [Store].[All Stores].[USA]) 275 * (Gender.[All Gender], [Store].[All Stores].[CANADA]) 276 * 277 * @param tuples Tuples 278 * @param reader Schema reader 279 * @param baseCubeForMeasure Cube 280 * @return xxxx 281 */ 282 public static List<Member[]> optimizeChildren( 283 List<Member[]> tuples, 284 SchemaReader reader, 285 Cube baseCubeForMeasure) 286 { 287 Map<Member, Integer>[] membersOccurencesInTuple = 288 membersVersusOccurencesInTuple(tuples); 289 int tupleLength = tuples.get(0).length; 290 291 //noinspection unchecked 292 Set<Member>[] sets = new HashSet[tupleLength]; 293 boolean optimized = false; 294 for (int i = 0; i < tupleLength; i++) { 295 if (areOccurencesEqual(membersOccurencesInTuple[i].values())) { 296 Set<Member> members = membersOccurencesInTuple[i].keySet(); 297 int originalSize = members.size(); 298 sets[i] = 299 optimizeMemberSet( 300 new HashSet<Member>(members), 301 reader, 302 baseCubeForMeasure); 303 if (sets[i].size() != originalSize) { 304 optimized = true; 305 } 306 } 307 } 308 if (optimized) { 309 if (sets.length == 1) { 310 Set<Member> set = sets[0]; 311 List<Member[]> tupleList = 312 new ArrayList<Member[]>(set.size()); 313 for (Member member : set) { 314 tupleList.add(new Member[] {member}); 315 } 316 return tupleList; 317 } 318 return crossProd(sets); 319 } 320 return tuples; 321 } 322 323 /** 324 * Finds member occurrences in tuple and generates a map of Members 325 * versus their occurrences in tuples. 326 * 327 * @param tuples List of tuples 328 * @return Map of the number of occurrences of each member in a tuple 329 */ 330 public static Map<Member, Integer>[] membersVersusOccurencesInTuple( 331 List<Member[]> tuples) 332 { 333 334 int tupleLength = tuples.get(0).length; 335 //noinspection unchecked 336 Map<Member, Integer>[] counters = new Map[tupleLength]; 337 for (int i = 0; i < counters.length; i++) { 338 counters[i] = new HashMap<Member, Integer>(); 339 } 340 for (Member[] tuple : tuples) { 341 for (int i = 0; i < tuple.length; i++) { 342 Member member = tuple[i]; 343 Map<Member, Integer> map = counters[i]; 344 if (map.containsKey(member)) { 345 Integer count = map.get(member); 346 map.put(member, ++count); 347 } else { 348 map.put(member, 1); 349 } 350 } 351 } 352 return counters; 353 } 354 355 private static Set<Member> optimizeMemberSet( 356 Set<Member> members, 357 SchemaReader reader, 358 Cube baseCubeForMeasure) 359 { 360 361 boolean didOptimize; 362 Set<Member> membersToBeOptimized = new HashSet<Member>(); 363 Set<Member> optimizedMembers = new HashSet<Member>(); 364 while (members.size() > 0) { 365 Iterator<Member> iterator = members.iterator(); 366 Member first = iterator.next(); 367 if (first.isAll()) { 368 optimizedMembers.clear(); 369 optimizedMembers.add(first); 370 return optimizedMembers; 371 } 372 membersToBeOptimized.add(first); 373 iterator.remove(); 374 375 Member firstParentMember = first.getParentMember(); 376 while (iterator.hasNext()) { 377 Member current = iterator.next(); 378 if (current.isAll()) { 379 optimizedMembers.clear(); 380 optimizedMembers.add(current); 381 return optimizedMembers; 382 } 383 384 Member currentParentMember = current.getParentMember(); 385 if (firstParentMember == null && 386 currentParentMember == null || 387 (firstParentMember != null && 388 firstParentMember.equals(currentParentMember))) { 389 membersToBeOptimized.add(current); 390 iterator.remove(); 391 } 392 } 393 394 int childCountOfParent = -1; 395 if (firstParentMember != null) { 396 childCountOfParent = getChildCount(firstParentMember, reader); 397 } 398 if (childCountOfParent != -1 && 399 membersToBeOptimized.size() == childCountOfParent && 400 canOptimize(firstParentMember,baseCubeForMeasure)) { 401 optimizedMembers.add(firstParentMember); 402 didOptimize = true; 403 } else { 404 optimizedMembers.addAll(membersToBeOptimized); 405 didOptimize = false; 406 } 407 membersToBeOptimized.clear(); 408 409 if (members.size() == 0 && didOptimize) { 410 Set temp = members; 411 members = optimizedMembers; 412 optimizedMembers = temp; 413 } 414 } 415 return optimizedMembers; 416 } 417 418 /** 419 * Returns whether tuples are equal. They must have the same length. 420 * 421 * @param tuple1 First tuple 422 * @param tuple2 Second tuple 423 * @return whether tuples are equal 424 */ 425 private static boolean isEqual(Member[] tuple1, Member[] tuple2) { 426 for (int i = 0; i < tuple1.length; i++) { 427 if (!tuple1[i].getUniqueName(). 428 equals(tuple2[i].getUniqueName())) { 429 return false; 430 } 431 } 432 return true; 433 } 434 435 private static boolean canOptimize( 436 Member parentMember, 437 Cube baseCube) 438 { 439 return dimensionJoinsToBaseCube( 440 parentMember.getDimension(), baseCube) 441 || !parentMember.isAll(); 442 } 443 444 private static List<Member[]> crossProd(Set<Member>[] sets) { 445 List<Member> firstList = new ArrayList<Member>(sets[0]); 446 List<Member> secondList = new ArrayList<Member>(sets[1]); 447 List<Member[]> tupleList = 448 CrossJoinFunDef.crossJoin(firstList, secondList); 449 for (int i = 2; i < sets.length; i++) { 450 Set<Member> set = sets[i]; 451 tupleList = 452 CrossJoinFunDef.crossJoin( 453 tupleList, new ArrayList<Member>(set)); 454 } 455 return tupleList; 456 } 457 458 private static boolean dimensionJoinsToBaseCube( 459 Dimension dimension, 460 Cube baseCube) 461 { 462 HashSet<Dimension> dimensions = new HashSet<Dimension>(); 463 dimensions.add(dimension); 464 return baseCube.nonJoiningDimensions(dimensions).size() == 0; 465 } 466 467 private static int getChildCount( 468 Member parentMember, 469 SchemaReader reader) 470 { 471 int childrenCountFromCache = 472 reader.getChildrenCountFromCache(parentMember); 473 if (childrenCountFromCache != -1) { 474 return childrenCountFromCache; 475 } 476 return reader.getMemberChildren(parentMember).size(); 477 } 478 479 } 480 } 481 482 // End AggregateFunDef.java