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 Julian Hyde 008// Copyright (C) 2005-2011 Pentaho 009// All Rights Reserved. 010*/ 011package mondrian.olap.fun; 012 013import mondrian.calc.*; 014import mondrian.calc.impl.*; 015import mondrian.mdx.ResolvedFunCall; 016import mondrian.olap.*; 017 018import java.util.*; 019 020/** 021 * Definition of the <code>Order</code> MDX function. 022 * 023 * @author jhyde 024 * @since Mar 23, 2006 025 */ 026class OrderFunDef extends FunDefBase { 027 028 static final ResolverImpl Resolver = new ResolverImpl(); 029 030 public OrderFunDef(ResolverBase resolverBase, int type, int[] types) { 031 super(resolverBase, type, types); 032 } 033 034 public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { 035 final IterCalc listCalc = compiler.compileIter(call.getArg(0)); 036 List<SortKeySpec> keySpecList = new ArrayList<SortKeySpec>(); 037 buildKeySpecList(keySpecList, call, compiler); 038 final int keySpecCount = keySpecList.size(); 039 Calc[] calcList = new Calc[keySpecCount + 1]; // +1 for the listCalc 040 calcList[0] = listCalc; 041 042 assert keySpecCount >= 1; 043 final Calc expCalc = keySpecList.get(0).getKey(); 044 calcList[1] = expCalc; 045 if (keySpecCount == 1) { 046 if (expCalc.isWrapperFor(MemberValueCalc.class) 047 || expCalc.isWrapperFor(MemberArrayValueCalc.class)) 048 { 049 List<MemberCalc> constantList = new ArrayList<MemberCalc>(); 050 List<MemberCalc> variableList = new ArrayList<MemberCalc>(); 051 final MemberCalc[] calcs = 052 (MemberCalc[]) ((AbstractCalc) expCalc).getCalcs(); 053 for (MemberCalc memberCalc : calcs) { 054 if (memberCalc.isWrapperFor(ConstantCalc.class) 055 && !listCalc.dependsOn( 056 memberCalc.getType().getHierarchy())) 057 { 058 constantList.add(memberCalc); 059 } else { 060 variableList.add(memberCalc); 061 } 062 } 063 if (constantList.isEmpty()) { 064 // All members are non-constant -- cannot optimize 065 } else if (variableList.isEmpty()) { 066 // All members are constant. Optimize by setting entire 067 // context first. 068 calcList[1] = new ValueCalc( 069 new DummyExp(expCalc.getType())); 070 return new ContextCalc( 071 calcs, 072 new CalcImpl( 073 call, calcList, keySpecList)); 074 } else { 075 // Some members are constant. Evaluate these before 076 // evaluating the list expression. 077 calcList[1] = MemberValueCalc.create( 078 new DummyExp(expCalc.getType()), 079 variableList.toArray( 080 new MemberCalc[variableList.size()]), 081 compiler.getEvaluator() 082 .mightReturnNullForUnrelatedDimension()); 083 return new ContextCalc( 084 constantList.toArray( 085 new MemberCalc[constantList.size()]), 086 new CalcImpl( 087 call, calcList, keySpecList)); 088 } 089 } 090 } 091 for (int i = 1; i < keySpecCount; i++) { 092 final Calc expCalcs = keySpecList.get(i).getKey(); 093 calcList[i + 1] = expCalcs; 094 } 095 return new CalcImpl(call, calcList, keySpecList); 096 } 097 098 private void buildKeySpecList( 099 List<SortKeySpec> keySpecList, 100 ResolvedFunCall call, 101 ExpCompiler compiler) 102 { 103 final int argCount = call.getArgs().length; 104 int j = 1; // args[0] is the input set 105 Calc key; 106 Flag dir; 107 Exp arg; 108 while (j < argCount) { 109 arg = call.getArg(j); 110 key = compiler.compileScalar(arg, true); 111 j++; 112 if ((j >= argCount) 113 || (call.getArg(j).getCategory() != Category.Symbol)) 114 { 115 dir = Flag.ASC; 116 } else { 117 dir = getLiteralArg(call, j, Flag.ASC, Flag.class); 118 j++; 119 } 120 keySpecList.add(new SortKeySpec(key, dir)); 121 } 122 } 123 124 private interface CalcWithDual extends Calc { 125 public TupleList evaluateDual( 126 Evaluator rootEvaluator, 127 Evaluator subEvaluator); 128 } 129 130 private static class CalcImpl 131 extends AbstractListCalc 132 implements CalcWithDual 133 { 134 private final IterCalc iterCalc; 135 private final Calc sortKeyCalc; 136 private final List<SortKeySpec> keySpecList; 137 private final int originalKeySpecCount; 138 private final int arity; 139 140 public CalcImpl( 141 ResolvedFunCall call, 142 Calc[] calcList, 143 List<SortKeySpec> keySpecList) 144 { 145 super(call, calcList); 146// assert iterCalc.getResultStyle() == ResultStyle.MUTABLE_LIST; 147 this.iterCalc = (IterCalc) calcList[0]; 148 this.sortKeyCalc = calcList[1]; 149 this.keySpecList = keySpecList; 150 this.originalKeySpecCount = keySpecList.size(); 151 this.arity = getType().getArity(); 152 } 153 154 public TupleList evaluateDual( 155 Evaluator rootEvaluator, Evaluator subEvaluator) 156 { 157 assert originalKeySpecCount == 1; 158 final TupleIterable iterable = 159 iterCalc.evaluateIterable(rootEvaluator); 160 // REVIEW: If iterable happens to be a list, we'd like to pass it, 161 // but we cannot yet guarantee that it is mutable. 162 final TupleList list = 163 iterable instanceof TupleList && false 164 ? (TupleList) iterable 165 : null; 166 Util.discard(iterCalc.getResultStyle()); 167 Flag sortKeyDir = keySpecList.get(0).getDirection(); 168 final TupleList tupleList; 169 final int savepoint = subEvaluator.savepoint(); 170 try { 171 subEvaluator.setNonEmpty(false); 172 if (arity == 1) { 173 tupleList = 174 new UnaryTupleList( 175 sortMembers( 176 subEvaluator, 177 iterable.slice(0), 178 list == null 179 ? null 180 : list.slice(0), 181 sortKeyCalc, 182 sortKeyDir.descending, 183 sortKeyDir.brk)); 184 } else { 185 tupleList = sortTuples( 186 subEvaluator, 187 iterable, 188 list, 189 sortKeyCalc, 190 sortKeyDir.descending, 191 sortKeyDir.brk, 192 arity); 193 } 194 return tupleList; 195 } finally { 196 subEvaluator.restore(savepoint); 197 } 198 } 199 200 public TupleList evaluateList(Evaluator evaluator) { 201 final TupleIterable iterable = 202 iterCalc.evaluateIterable(evaluator); 203 // REVIEW: If iterable happens to be a list, we'd like to pass it, 204 // but we cannot yet guarantee that it is mutable. 205 final TupleList list = 206 iterable instanceof TupleList && false 207 ? (TupleList) iterable 208 : null; 209 // go by size of keySpecList before purging 210 if (originalKeySpecCount == 1) { 211 Flag sortKeyDir = keySpecList.get(0).getDirection(); 212 final TupleList tupleList; 213 final int savepoint = evaluator.savepoint(); 214 try { 215 evaluator.setNonEmpty(false); 216 if (arity == 1) { 217 tupleList = 218 new UnaryTupleList( 219 sortMembers( 220 evaluator, 221 iterable.slice(0), 222 list == null ? null : list.slice(0), 223 sortKeyCalc, 224 sortKeyDir.descending, 225 sortKeyDir.brk)); 226 } else { 227 tupleList = 228 sortTuples( 229 evaluator, 230 iterable, 231 list, 232 sortKeyCalc, 233 sortKeyDir.descending, 234 sortKeyDir.brk, 235 arity); 236 } 237 return tupleList; 238 } finally { 239 evaluator.restore(savepoint); 240 } 241 } else { 242 purgeKeySpecList(keySpecList, list); 243 if (keySpecList.isEmpty()) { 244 return list; 245 } 246 final TupleList tupleList; 247 final int savepoint = evaluator.savepoint(); 248 try { 249 evaluator.setNonEmpty(false); 250 if (arity == 1) { 251 tupleList = 252 new UnaryTupleList( 253 sortMembers( 254 evaluator, 255 iterable.slice(0), 256 list == null ? null : list.slice(0), 257 keySpecList)); 258 } else { 259 tupleList = 260 sortTuples( 261 evaluator, 262 iterable, 263 list, 264 keySpecList, 265 arity); 266 } 267 return tupleList; 268 } finally { 269 evaluator.restore(savepoint); 270 } 271 } 272 } 273 274 public void collectArguments(Map<String, Object> arguments) { 275 super.collectArguments(arguments); 276 277 // only good for original Order syntax 278 assert originalKeySpecCount == 1; 279 Flag sortKeyDir = keySpecList.get(0).getDirection(); 280 arguments.put( 281 "direction", 282 (sortKeyDir.descending 283 ? (sortKeyDir.brk ? Flag.BDESC : Flag.DESC) 284 : (sortKeyDir.brk ? Flag.BASC : Flag.ASC))); 285 } 286 287 public boolean dependsOn(Hierarchy hierarchy) { 288 return anyDependsButFirst(getCalcs(), hierarchy); 289 } 290 291 private void purgeKeySpecList( 292 List<SortKeySpec> keySpecList, 293 TupleList list) 294 { 295 if (list == null || list.isEmpty()) { 296 return; 297 } 298 if (keySpecList.size() == 1) { 299 return; 300 } 301 List<Hierarchy> listHierarchies = 302 new ArrayList<Hierarchy>(list.getArity()); 303 for (Member member : list.get(0)) { 304 listHierarchies.add(member.getHierarchy()); 305 } 306 // do not sort (remove sort key spec from the list) if 307 // 1. <member_value_expression> evaluates to a member from a 308 // level/dimension which is not used in the first argument 309 // 2. <member_value_expression> evaluates to the same member for 310 // all cells; for example, a report showing all quarters of 311 // year 1998 will not be sorted if the sort key is on the constant 312 // member [1998].[Q1] 313 ListIterator<SortKeySpec> iter = keySpecList.listIterator(); 314 while (iter.hasNext()) { 315 SortKeySpec key = iter.next(); 316 Calc expCalc = key.getKey(); 317 if (expCalc instanceof MemberOrderKeyFunDef.CalcImpl) { 318 Calc[] calcs = 319 ((MemberOrderKeyFunDef.CalcImpl) expCalc).getCalcs(); 320 MemberCalc memberCalc = (MemberCalc) calcs[0]; 321 if (memberCalc instanceof ConstantCalc 322 || !listHierarchies.contains( 323 memberCalc.getType().getHierarchy())) 324 { 325 iter.remove(); 326 } 327 } 328 } 329 } 330 } 331 332 private static class ContextCalc extends GenericIterCalc { 333 private final MemberCalc[] memberCalcs; 334 private final CalcWithDual calc; 335 private final Member[] members; // workspace 336 337 protected ContextCalc(MemberCalc[] memberCalcs, CalcWithDual calc) { 338 super(new DummyExp(calc.getType()), xx(memberCalcs, calc)); 339 this.memberCalcs = memberCalcs; 340 this.calc = calc; 341 this.members = new Member[memberCalcs.length]; 342 } 343 344 private static Calc[] xx( 345 MemberCalc[] memberCalcs, CalcWithDual calc) 346 { 347 Calc[] calcs = new Calc[memberCalcs.length + 1]; 348 System.arraycopy(memberCalcs, 0, calcs, 0, memberCalcs.length); 349 calcs[calcs.length - 1] = calc; 350 return calcs; 351 } 352 353 public Object evaluate(Evaluator evaluator) { 354 // Evaluate each of the members, and set as context in the 355 // sub-evaluator. 356 for (int i = 0; i < memberCalcs.length; i++) { 357 members[i] = memberCalcs[i].evaluateMember(evaluator); 358 } 359 final Evaluator subEval = evaluator.push(members); 360 // Evaluate the expression in the new context. 361 return calc.evaluateDual(evaluator, subEval); 362 } 363 364 public boolean dependsOn(Hierarchy hierarchy) { 365 if (anyDepends(memberCalcs, hierarchy)) { 366 return true; 367 } 368 // Member calculations generate members, which mask the actual 369 // expression from the inherited context. 370 for (MemberCalc memberCalc : memberCalcs) { 371 if (memberCalc.getType().usesHierarchy(hierarchy, true)) { 372 return false; 373 } 374 } 375 return calc.dependsOn(hierarchy); 376 } 377 378 public ResultStyle getResultStyle() { 379 return calc.getResultStyle(); 380 } 381 } 382 383 private static class ResolverImpl extends ResolverBase { 384 private final String[] reservedWords; 385 static int[] argTypes; 386 387 private ResolverImpl() { 388 super( 389 "Order", 390 "Order(<Set> {, <Key Specification>}...)", 391 "Arranges members of a set, optionally preserving or breaking the hierarchy.", 392 Syntax.Function); 393 this.reservedWords = Flag.getNames(); 394 } 395 396 public FunDef resolve( 397 Exp[] args, 398 Validator validator, 399 List<Conversion> conversions) 400 { 401 argTypes = new int[args.length]; 402 403 if (args.length < 2) { 404 return null; 405 } 406 // first arg must be a set 407 if (!validator.canConvert(0, args[0], Category.Set, conversions)) { 408 return null; 409 } 410 argTypes[0] = Category.Set; 411 // after fist args, should be: value [, symbol] 412 int i = 1; 413 while (i < args.length) { 414 if (!validator.canConvert( 415 i, args[i], Category.Value, conversions)) 416 { 417 return null; 418 } else { 419 argTypes[i] = Category.Value; 420 i++; 421 } 422 // if symbol is not specified, skip to the next 423 if ((i == args.length)) { 424 //done, will default last arg to ASC 425 } else { 426 if (!validator.canConvert( 427 i, args[i], Category.Symbol, conversions)) 428 { 429 // continue, will default sort flag for prev arg to ASC 430 } else { 431 argTypes[i] = Category.Symbol; 432 i++; 433 } 434 } 435 } 436 return new OrderFunDef(this, Category.Set, argTypes); 437 } 438 439 public String[] getReservedWords() { 440 if (reservedWords != null) { 441 return reservedWords; 442 } 443 return super.getReservedWords(); 444 } 445 } 446} 447 448// End OrderFunDef.java