001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapResult.java#123 $ 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) 2001-2002 Kana Software, Inc. 007 // Copyright (C) 2001-2008 Julian Hyde and others 008 // All Rights Reserved. 009 // You must accept the terms of that agreement to use this software. 010 // 011 // jhyde, 10 August, 2001 012 */ 013 014 package mondrian.rolap; 015 import java.util.ArrayList; 016 import java.util.Collections; 017 import java.util.HashMap; 018 import java.util.Iterator; 019 import java.util.List; 020 import java.util.ListIterator; 021 import java.util.Locale; 022 import java.util.Map; 023 024 import mondrian.calc.Calc; 025 import mondrian.calc.DummyExp; 026 import mondrian.calc.ParameterSlot; 027 import mondrian.calc.ResultStyle; 028 import mondrian.calc.impl.ValueCalc; 029 import mondrian.olap.Axis; 030 import mondrian.olap.Cell; 031 import mondrian.olap.CellFormatter; 032 import mondrian.olap.Cube; 033 import mondrian.olap.Dimension; 034 import mondrian.olap.DimensionType; 035 import mondrian.olap.Evaluator; 036 import mondrian.olap.Exp; 037 import mondrian.olap.Hierarchy; 038 import mondrian.olap.Member; 039 import mondrian.olap.MondrianProperties; 040 import mondrian.olap.Parameter; 041 import mondrian.olap.Position; 042 import mondrian.olap.Query; 043 import mondrian.olap.QueryAxis; 044 import mondrian.olap.ResultBase; 045 import mondrian.olap.ResultLimitExceededException; 046 import mondrian.olap.SchemaReader; 047 import mondrian.olap.Util; 048 import mondrian.olap.fun.FunUtil; 049 import mondrian.olap.fun.MondrianEvaluationException; 050 import mondrian.olap.type.ScalarType; 051 import mondrian.resource.MondrianResource; 052 import mondrian.rolap.agg.AggregationManager; 053 import mondrian.util.ConcatenableList; 054 import mondrian.util.Format; 055 import mondrian.util.ObjectPool; 056 057 import org.apache.log4j.Logger; 058 059 /** 060 * A <code>RolapResult</code> is the result of running a query. 061 * 062 * @author jhyde 063 * @since 10 August, 2001 064 * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapResult.java#123 $ 065 */ 066 public class RolapResult extends ResultBase { 067 068 private static final Logger LOGGER = Logger.getLogger(ResultBase.class); 069 070 private RolapEvaluator evaluator; 071 private final CellKey point; 072 073 private CellInfoContainer cellInfos; 074 private FastBatchingCellReader batchingReader; 075 private final CellReader aggregatingReader = 076 AggregationManager.instance().getCacheCellReader(); 077 private Modulos modulos = null; 078 private final int maxEvalDepth = 079 MondrianProperties.instance().MaxEvalDepth.get(); 080 081 private final Map<Integer, Boolean> positionsHighCardinality = 082 new HashMap<Integer, Boolean>(); 083 private final Map<Integer, Iterator<Position>> positionsIterators = 084 new HashMap<Integer, Iterator<Position>>(); 085 private final Map<Integer, Integer> positionsIndexes = 086 new HashMap<Integer, Integer>(); 087 private final Map<Integer, List<Position>> positionsCurrent = 088 new HashMap<Integer, List<Position>>(); 089 090 RolapResult(Query query, boolean execute) { 091 super(query, new Axis[query.axes.length]); 092 093 this.point = CellKey.Generator.newCellKey(query.axes.length); 094 final int expDeps = MondrianProperties.instance().TestExpDependencies.get(); 095 if (expDeps > 0) { 096 this.evaluator = new RolapDependencyTestingEvaluator(this, expDeps); 097 } else { 098 final RolapEvaluator.RolapEvaluatorRoot root = 099 new RolapResultEvaluatorRoot(this); 100 this.evaluator = new RolapEvaluator(root); 101 } 102 RolapCube rcube = (RolapCube) query.getCube(); 103 this.batchingReader = new FastBatchingCellReader(rcube); 104 105 this.cellInfos = (query.axes.length > 4) 106 ? new CellInfoMap(point) : new CellInfoPool(query.axes.length); 107 108 109 if (!execute) { 110 return; 111 } 112 113 boolean normalExecution = true; 114 try { 115 // This call to clear the cube's cache only has an 116 // effect if caching has been disabled, otherwise 117 // nothing happens. 118 // Clear the local cache before a query has run 119 rcube.clearCachedAggregations(); 120 // Check if there are modifications to the global aggregate cache 121 rcube.checkAggregateModifications(); 122 123 124 ///////////////////////////////////////////////////////////////// 125 // 126 // Evaluation Algorithm 127 // 128 // There are three basic steps to the evaluation algorithm: 129 // 1) Determine all Members for each axis but do not save 130 // information (do not build the RolapAxis), 131 // 2) Save all Members for each axis (build RolapAxis). 132 // 3) Evaluate and store each Cell determined by the Members 133 // of the axes. 134 // Step 1 converges on the stable set of Members pre axis. 135 // Steps 1 and 2 make sure that the data has been loaded. 136 // 137 // More detail follows. 138 // 139 // Explicit and Implicit Members: 140 // A Member is said to be 'explicit' if it appears on one of 141 // the Axes (one of the RolapAxis Position List of Members). 142 // A Member is 'implicit' if it is in the query but does not 143 // end up on any Axes (its usage, for example, is in a function). 144 // When for a Dimension none of its Members are explicit in the 145 // query, then the default Member is used which is like putting 146 // the Member in the Slicer. 147 // 148 // Special Dimensions: 149 // There are 2 special dimensions. 150 // The first is the Time dimension. If in a schema there is 151 // no ALL Member, then Whatever happens to be the default 152 // Member is used if Time Members are not explicitly set 153 // in the query. 154 // The second is the Measures dimension. This dimension 155 // NEVER has an ALL Member. A cube's default Measure is set 156 // by convention - its simply the first Measure defined in the 157 // cube. 158 // 159 // First a RolapEvaluator is created. During its creation, 160 // it gets a Member from each Hierarchy. Each Member is the 161 // default Member of the Hierarchy. For most Hierarchies this 162 // Member is the ALL Member, but there are cases where 1) 163 // a Hierarchy does not have an ALL Member or 2) the Hierarchy 164 // has an ALL Member but that Member is not the default Member. 165 // In these cases, the default Member is still used, but its 166 // use can cause evaluation issues (seemingly strange evaluation 167 // results). 168 // 169 // Next, load all root Members for Hierarchies that have no ALL 170 // Member and load ALL Members that are not the default Member. 171 // 172 // Determine the Members of the Slicer axis (Step 1 above). Any 173 // Members found are added to the AxisMember object. If one of these 174 // Members happens to be a Measure, then the Slicer is explicitly 175 // specifying the query's Measure and this should be put into the 176 // evaluator's context (replacing the default Measure which just 177 // happens to be the first Measure defined in the cube). Other 178 // Members found in the AxisMember object are also placed into the 179 // evaluator's context since these also are explicitly specified. 180 // Also, any other Members in the AxisMember object which have the 181 // same Hierarchy as Members in the list of root Members for 182 // Hierarchies that have no ALL Member, replace those Members - they 183 // Slicer has explicitly determined which ones to use. The 184 // AxisMember object is now cleared. 185 // The Slicer does not depend upon the other Axes, but the other 186 // Axes depend upon both the Slicer and each other. 187 // 188 // The AxisMember object also checks if the number of Members 189 // exceeds the ResultLimit property throwing a 190 // TotalMembersLimitExceeded Exception if it does. 191 // 192 // For all non-Slicer axes, the Members are determined (Step 1 193 // above). If a Measure is found in the AxisMember, then an 194 // Axis is explicitly specifying a Measure. 195 // If any Members in the AxisMember object have the same Hierarchy 196 // as a Member in the set of root Members for Hierarchies that have 197 // no ALL Member, then replace those root Members with the Member 198 // from the AxisMember object. In this case, again, a Member 199 // was explicitly specified in an Axis. If this replacement 200 // occurs, then one must redo this step with the new Members. 201 // 202 // Now Step 3 above is done. First to the Slicer Axis and then 203 // to the other Axes. Here the Axes are actually generated. 204 // If a Member of an Axis is an Calculated Member (and the 205 // Calculated Member is not a Member of the Measure Hierarchy), 206 // then find the Dimension associated with the Calculated 207 // Member and remove Members with the same Dimension in the set of 208 // root Members for Hierarchies that have no ALL Member. 209 // This is done because via the Calculated Member the Member 210 // was implicitly specified in the query. If this removal occurs, 211 // then the Axes must be re-evaluated repeating Step 3. 212 // 213 ///////////////////////////////////////////////////////////////// 214 215 216 // The AxisMember object is used to hold Members that are found 217 // during Step 1 when the Axes are determined. 218 final AxisMember axisMembers = new AxisMember(); 219 220 221 // list of ALL Members that are not default Members 222 final List<Member> nonDefaultAllMembers = new ArrayList<Member>(); 223 224 // List of Members of Hierarchies that do not have an ALL Member 225 List<List<Member>> nonAllMembers = new ArrayList<List<Member>>(); 226 227 // List of Measures 228 final List<Member> measureMembers = new ArrayList<Member>(); 229 230 // load all root Members for Hierarchies that have no ALL 231 // Member and load ALL Members that are not the default Member. 232 // Also, all Measures are are gathered. 233 loadSpecialMembers(nonDefaultAllMembers, 234 nonAllMembers, measureMembers); 235 236 // clear evaluation cache 237 query.clearEvalCache(); 238 239 // Save, may be needed by some Expression Calc's 240 query.putEvalCache("ALL_MEMBER_LIST", nonDefaultAllMembers); 241 242 243 final List<List<Member>> emptyNonAllMembers = Collections.emptyList(); 244 245 ///////////////////////////////////////////////////////////////// 246 // 247 // Determine Slicer 248 // 249 axisMembers.setSlicer(true); 250 loadMembers(emptyNonAllMembers, evaluator, 251 query.slicerAxis, query.slicerCalc, axisMembers); 252 axisMembers.setSlicer(false); 253 254 if (! axisMembers.isEmpty()) { 255 for (Member m : axisMembers) { 256 if (m == null) { 257 break; 258 } 259 evaluator.setSlicerContext(m); 260 if (m.isMeasure()) { 261 // A Measure was explicitly declared in the 262 // Slicer, don't need to worry about Measures 263 // for this query. 264 measureMembers.clear(); 265 } 266 } 267 replaceNonAllMembers(nonAllMembers, axisMembers); 268 axisMembers.clearMembers(); 269 } 270 // 271 ///////////////////////////////////////////////////////////////// 272 273 274 ///////////////////////////////////////////////////////////////// 275 // 276 // Determine Axes 277 // 278 boolean changed = false; 279 280 // reset to total member count 281 axisMembers.clearTotalCellCount(); 282 283 for (int i = 0; i < axes.length; i++) { 284 final QueryAxis axis = query.axes[i]; 285 final Calc calc = query.axisCalcs[i]; 286 loadMembers(emptyNonAllMembers, evaluator, 287 axis, calc, axisMembers); 288 } 289 290 if (! axisMembers.isEmpty()) { 291 for (Member m : axisMembers) { 292 if (m.isMeasure()) { 293 // A Measure was explicitly declared on an 294 // axis, don't need to worry about Measures 295 // for this query. 296 measureMembers.clear(); 297 } 298 } 299 changed = replaceNonAllMembers(nonAllMembers, axisMembers); 300 axisMembers.clearMembers(); 301 } 302 303 /* 304 // This code allows replacing default Measure Member 305 // with one found in the query, an implicit Measure. 306 // Fixes some problems (RolapResultTest._testNullDefaultMeasure) 307 // but causes other ones (NamedSetTest.testNamedSetUsedInCrossJoin) 308 // so it can not be used. 309 Member previous = null; 310 if (! measureMembers.isEmpty()) { 311 MeasureVisitor visitor = new MeasureVisitor(); 312 for (int i = 0; i < axes.length; i++) { 313 query.axes[i].accept(visitor); 314 } 315 println("Measures on Axis:"); 316 for (Member m : visitor.measures) { 317 println(" m=" +m.getUniqueName()); 318 } 319 if (! visitor.measures.isEmpty()) { 320 Member m = visitor.measures.get(0); 321 previous = evaluator.setContext(m); 322 changed |= ! m.equals(previous); 323 } 324 } 325 // move this 326 // it should be set just before calling executeBody 327 // evaluator.setContext(previous); 328 */ 329 330 331 if (changed) { 332 // only count number of members, do not collect any 333 axisMembers.countOnly(true); 334 // reset to total member count 335 axisMembers.clearTotalCellCount(); 336 337 for (int i = 0; i < axes.length; i++) { 338 final QueryAxis axis = query.axes[i]; 339 final Calc calc = query.axisCalcs[i]; 340 loadMembers( 341 nonAllMembers, 342 evaluator.push(), 343 axis, calc, axisMembers); 344 } 345 } 346 347 // throws exception if number of members exceeds limit 348 axisMembers.checkLimit(); 349 350 // 351 ///////////////////////////////////////////////////////////////// 352 353 ///////////////////////////////////////////////////////////////// 354 // 355 // Execute Slicer 356 // 357 this.slicerAxis = 358 evalExecute( 359 nonAllMembers, 360 nonAllMembers.size() - 1, 361 evaluator.push(), 362 query.slicerAxis, 363 query.slicerCalc); 364 365 // Use the context created by the slicer for the other 366 // axes. For example, "select filter([Customers], [Store 367 // Sales] > 100) on columns from Sales where 368 // ([Time].[1998])" should show customers whose 1998 (not 369 // total) purchases exceeded 100. 370 371 // Getting the Position list's size and the Position 372 // at index == 0 will, in fact, cause an Iterable-base 373 // Axis Position List to become a List-base Axis 374 // Position List (and increae memory usage), but for 375 // the slicer axis, the number of Positions is very 376 // small, so who cares. 377 switch (this.slicerAxis.getPositions().size()) { 378 case 0: 379 throw MondrianResource.instance().EmptySlicer.ex(); 380 case 1: 381 break; 382 default: 383 throw MondrianResource.instance().CompoundSlicer.ex(); 384 } 385 // 386 ///////////////////////////////////////////////////////////////// 387 388 ///////////////////////////////////////////////////////////////// 389 // 390 // Execute Axes 391 // 392 boolean redo = true; 393 while (redo) { 394 RolapEvaluator e = evaluator.push(); 395 redo = false; 396 397 for (int i = 0; i < axes.length; i++) { 398 QueryAxis axis = query.axes[i]; 399 final Calc calc = query.axisCalcs[i]; 400 Axis axisResult = 401 evalExecute( 402 nonAllMembers, 403 nonAllMembers.size() - 1, e, axis, calc); 404 405 if (! nonAllMembers.isEmpty()) { 406 List<Position> pl = axisResult.getPositions(); 407 if (!pl.isEmpty()) { 408 // Only need to process the first Position 409 Position p = pl.get(0); 410 for (Member m : p) { 411 if (m.isCalculated()) { 412 CalculatedMeasureVisitor visitor = 413 new CalculatedMeasureVisitor(); 414 m.getExpression().accept(visitor); 415 Dimension dimension = visitor.dimension; 416 redo = removeDimension(dimension, nonAllMembers); 417 } 418 } 419 } 420 } 421 this.axes[i] = axisResult; 422 } 423 } 424 // 425 ///////////////////////////////////////////////////////////////// 426 427 ///////////////////////////////////////////////////////////////// 428 // 429 // Get value for each Cell 430 // 431 executeBody(this.query, new int[axes.length]); 432 // 433 ///////////////////////////////////////////////////////////////// 434 435 // If you are very close to running out of memory due to 436 // the number of CellInfo's in cellInfos, then calling this 437 // may cause the out of memory one is trying to aviod. 438 // On the other hand, calling this can reduce the size of 439 // the ObjectPool's internal storage by half (but, of course, 440 // it will not reduce the size of the stored objects themselves). 441 // Only call this if there are lots of CellInfo. 442 if (this.cellInfos.size() > 10000) { 443 this.cellInfos.trimToSize(); 444 } 445 446 } catch (ResultLimitExceededException ex) { 447 // If one gets a ResultLimitExceededException, then 448 // don't count on anything being worth caching. 449 normalExecution = false; 450 451 // De-reference data structures that might be holding 452 // partial results but surely are taking up memory. 453 evaluator = null; 454 cellInfos = null; 455 batchingReader = null; 456 for (int i = 0; i < axes.length; i++) { 457 axes[i] = null; 458 } 459 slicerAxis = null; 460 461 query.clearEvalCache(); 462 463 throw ex; 464 465 } finally { 466 if (normalExecution) { 467 // Push all modifications to the aggregate cache to the global 468 // cache so each thread can start using it 469 rcube.pushAggregateModificationsToGlobalCache(); 470 471 // Expression cache duration is for each query. It is time to 472 // clear out the whole expression cache at the end of a query. 473 evaluator.clearExpResultCache(true); 474 } 475 if (LOGGER.isDebugEnabled()) { 476 LOGGER.debug("RolapResult<init>: " + Util.printMemory()); 477 } 478 } 479 } 480 protected boolean removeDimension(Dimension dimension, List<List<Member>> nonAllMembers) { 481 boolean changed = false; 482 for (ListIterator<List<Member>> it = nonAllMembers.listIterator(); 483 it.hasNext();) { 484 List<Member> ms = it.next(); 485 Dimension d = ms.get(0).getHierarchy().getDimension(); 486 if (d.equals(dimension)) { 487 it.remove(); 488 } 489 } 490 return changed; 491 } 492 493 private static class CalculatedMeasureVisitor extends mondrian.mdx.MdxVisitorImpl { 494 Dimension dimension; 495 CalculatedMeasureVisitor() { 496 } 497 public Object visit(mondrian.olap.Formula formula) { 498 return null; 499 } 500 public Object visit(mondrian.mdx.ResolvedFunCall call) { 501 return null; 502 } 503 public Object visit(mondrian.olap.Id id) { 504 return null; 505 } 506 public Object visit(mondrian.mdx.ParameterExpr parameterExpr) { 507 return null; 508 } 509 public Object visit(mondrian.mdx.DimensionExpr dimensionExpr) { 510 dimension = dimensionExpr.getDimension(); 511 return null; 512 } 513 public Object visit(mondrian.mdx.HierarchyExpr hierarchyExpr) { 514 Hierarchy hierarchy = hierarchyExpr.getHierarchy(); 515 dimension = hierarchy.getDimension(); 516 return null; 517 } 518 public Object visit(mondrian.mdx.LevelExpr levelExpr) { 519 return null; 520 } 521 public Object visit(mondrian.mdx.MemberExpr memberExpr) { 522 Member member = memberExpr.getMember(); 523 dimension = member.getHierarchy().getDimension(); 524 return null; 525 } 526 public Object visit(mondrian.mdx.NamedSetExpr namedSetExpr) { 527 return null; 528 } 529 public Object visit(mondrian.olap.Literal literal) { 530 return null; 531 } 532 } 533 534 protected boolean replaceNonAllMembers(List<List<Member>> nonAllMembers, 535 AxisMember axisMembers) { 536 537 boolean changed = false; 538 List<Member> mList = new ArrayList<Member>(); 539 for (ListIterator<List<Member>> it = nonAllMembers.listIterator(); 540 it.hasNext();) { 541 List<Member> ms = it.next(); 542 Hierarchy h = ms.get(0).getHierarchy(); 543 mList.clear(); 544 for (Member m : axisMembers) { 545 if (m.getHierarchy().equals(h)) { 546 mList.add(m); 547 } 548 } 549 if (! mList.isEmpty()) { 550 changed = true; 551 it.set(mList); 552 } 553 } 554 return changed; 555 556 } 557 558 protected void loadMembers(List<List<Member>> nonAllMembers, 559 RolapEvaluator evaluator, QueryAxis axis, Calc calc, 560 AxisMember axisMembers) { 561 int attempt = 0; 562 evaluator.setCellReader(batchingReader); 563 while (true) { 564 axisMembers.clearAxisCount(); 565 evalLoad( 566 nonAllMembers, nonAllMembers.size() - 1, 567 evaluator, axis, calc, axisMembers); 568 569 if (!batchingReader.loadAggregations(query)) { 570 break; 571 } else { 572 // Clear invalid expression result so that the next evaluation 573 // will pick up the newly loaded aggregates. 574 evaluator.clearExpResultCache(false); 575 } 576 577 if (attempt++ > maxEvalDepth) { 578 throw Util.newInternal( 579 "Failed to load all aggregations after " + 580 maxEvalDepth + 581 " passes; there's probably a cycle"); 582 } 583 } 584 } 585 586 void evalLoad(List<List<Member>> nonAllMembers, int cnt, 587 Evaluator evaluator, QueryAxis axis, Calc calc, 588 AxisMember axisMembers) { 589 if (cnt < 0) { 590 executeAxis(evaluator.push(), axis, calc, false, axisMembers); 591 } else { 592 for (Member m : nonAllMembers.get(cnt)) { 593 evaluator.setContext(m); 594 evalLoad(nonAllMembers, cnt - 1, evaluator, axis, calc, axisMembers); 595 } 596 } 597 } 598 Axis evalExecute(List<List<Member>> nonAllMembers, int cnt, 599 RolapEvaluator evaluator, QueryAxis axis, Calc calc) { 600 Axis axisResult = null; 601 if (cnt < 0) { 602 evaluator.setCellReader(aggregatingReader); 603 axisResult = 604 executeAxis(evaluator.push(), axis, calc, true, null); 605 // No need to clear expression cache here as no new aggregates are 606 // loaded(aggregatingReader reads from cache). 607 } else { 608 for (Member m : nonAllMembers.get(cnt)) { 609 evaluator.setContext(m); 610 Axis a = evalExecute(nonAllMembers, cnt - 1, evaluator, axis, calc); 611 boolean ordered = false; 612 if (axis != null) { 613 ordered = axis.isOrdered(); 614 } 615 axisResult = mergeAxes(axisResult, a, evaluator, ordered); 616 } 617 } 618 return axisResult; 619 } 620 621 /** 622 * Finds all root Members 1) whose Hierarchy does not have an ALL 623 * Member, 2) whose default Member is not the ALL Member and 3) 624 * all Measures. 625 * 626 * @param nonDefaultAllMembers List of all root Members for Hierarchies 627 * whose default Member is not the ALL Member. 628 * @param nonAllMembers List of root Members for Hierarchies that have no 629 * ALL Member. 630 * @param measureMembers List all Measures 631 */ 632 protected void loadSpecialMembers( 633 List<Member> nonDefaultAllMembers, 634 List<List<Member>> nonAllMembers, 635 List<Member> measureMembers) 636 { 637 SchemaReader schemaReader = evaluator.getSchemaReader(); 638 Member[] evalMembers = evaluator.getMembers(); 639 for (Member em : evalMembers) { 640 if (em.isCalculated()) { 641 continue; 642 } 643 Hierarchy h = em.getHierarchy(); 644 Dimension d = h.getDimension(); 645 if (d.getDimensionType() == DimensionType.TimeDimension) { 646 continue; 647 } 648 if (!em.isAll()) { 649 List<Member> rootMembers = schemaReader.getHierarchyRootMembers(h); 650 if (em.isMeasure()) { 651 for (Member mm : rootMembers) { 652 measureMembers.add(mm); 653 } 654 } else { 655 if (h.hasAll()) { 656 for (Member m : rootMembers) { 657 if (m.isAll()) { 658 nonDefaultAllMembers.add(m); 659 break; 660 } 661 } 662 } else { 663 nonAllMembers.add(rootMembers); 664 } 665 } 666 } 667 } 668 } 669 670 protected Logger getLogger() { 671 return LOGGER; 672 } 673 674 public final RolapCube getCube() { 675 return evaluator.getCube(); 676 } 677 678 // implement Result 679 public Axis[] getAxes() { 680 return axes; 681 } 682 683 /** 684 * Get the Cell for the given Cell position. 685 * 686 * @param pos Cell position. 687 * @return the Cell associated with the Cell position. 688 */ 689 public Cell getCell(int[] pos) { 690 if (pos.length != point.size()) { 691 throw Util.newError( 692 "coordinates should have dimension " + point.size()); 693 } 694 695 for (int i = 0; i < pos.length; i++) { 696 if (positionsHighCardinality.get(i)) { 697 executeBody(this.query, pos); 698 break; 699 } 700 } 701 702 CellInfo ci = cellInfos.lookup(pos); 703 if (ci.value == null) { 704 for (int i = 0; i < pos.length; i++) { 705 int po = pos[i]; 706 if (po < 0 || po >= axes[i].getPositions().size()) { 707 throw Util.newError("coordinates out of range"); 708 } 709 } 710 ci.value = Util.nullValue; 711 } 712 713 return new RolapCell(this, pos.clone(), ci); 714 } 715 716 private Axis executeAxis( 717 Evaluator evaluator, 718 QueryAxis axis, 719 Calc axisCalc, 720 boolean construct, 721 AxisMember axisMembers) 722 { 723 Axis axisResult = null; 724 if (axis == null) { 725 // Create an axis containing one position with no members (not 726 // the same as an empty axis). 727 if (construct) { 728 axisResult = new RolapAxis.SingleEmptyPosition(); 729 } 730 731 } else { 732 evaluator.setNonEmpty(axis.isNonEmpty()); 733 evaluator.setEvalAxes(true); 734 Object value = axisCalc.evaluate(evaluator); 735 if (axisCalc.getClass().getName().indexOf("OrderFunDef") != -1) { 736 axis.setOrdered(true); 737 } 738 evaluator.setNonEmpty(false); 739 if (value != null) { 740 // List or Iterable of Member or Member[] 741 if (value instanceof List) { 742 List<Object> list = (List) value; 743 if (construct) { 744 if (list.isEmpty()) { 745 // should be??? 746 axisResult = new RolapAxis.NoPosition(); 747 } else if (list.get(0) instanceof Member[]) { 748 axisResult = 749 new RolapAxis.MemberArrayList((List<Member[]>)value); 750 } else { 751 axisResult = 752 new RolapAxis.MemberList((List<Member>)value); 753 } 754 } else if (axisMembers != null) { 755 axisMembers.merge(list); 756 } 757 } else { 758 // Iterable 759 Iterable<Object> iter = (Iterable) value; 760 Iterator it = iter.iterator(); 761 if (construct) { 762 if (! it.hasNext()) { 763 axisResult = new RolapAxis.NoPosition(); 764 } else if (it.next() instanceof Member[]) { 765 axisResult = new RolapAxis.MemberArrayIterable( 766 (Iterable<Member[]>)value); 767 } else { 768 axisResult = new RolapAxis.MemberIterable( 769 (Iterable<Member>)value); 770 } 771 } else if (axisMembers != null) { 772 axisMembers.merge(it); 773 } 774 } 775 } 776 evaluator.setEvalAxes(false); 777 } 778 return axisResult; 779 } 780 781 private void executeBody(Query query, final int[] pos) { 782 // Compute the cells several times. The first time, use a dummy 783 // evaluator which collects requests. 784 int count = 0; 785 while (true) { 786 787 evaluator.setCellReader(batchingReader); 788 executeStripe(query.axes.length - 1, evaluator.push(), pos); 789 790 // Retrieve the aggregations collected. 791 // 792 if (!batchingReader.loadAggregations(query)) { 793 // We got all of the cells we needed, so the result must be 794 // correct. 795 return; 796 } else { 797 // Clear invalid expression result so that the next evaluation 798 // will pick up the newly loaded aggregates. 799 evaluator.clearExpResultCache(false); 800 } 801 802 if (count++ > maxEvalDepth) { 803 if (evaluator instanceof RolapDependencyTestingEvaluator) { 804 // The dependency testing evaluator can trigger new 805 // requests every cycle. So let is run as normal for 806 // the first N times, then run it disabled. 807 ((RolapDependencyTestingEvaluator.DteRoot) 808 evaluator.root).disabled = true; 809 if (count > maxEvalDepth * 2) { 810 throw Util.newInternal("Query required more than " 811 + count + " iterations"); 812 } 813 } else { 814 throw Util.newInternal("Query required more than " 815 + count + " iterations"); 816 } 817 } 818 819 cellInfos.clear(); 820 } 821 } 822 823 boolean isDirty() { 824 return batchingReader.isDirty(); 825 } 826 827 private Object evaluateExp(Calc calc, RolapEvaluator evaluator) { 828 int attempt = 0; 829 boolean dirty = batchingReader.isDirty(); 830 while (true) { 831 RolapEvaluator ev = evaluator.push(); 832 833 ev.setCellReader(batchingReader); 834 Object preliminaryValue = calc.evaluate(ev); 835 Util.discard(preliminaryValue); 836 837 if (!batchingReader.loadAggregations(evaluator.getQuery())) { 838 break; 839 } else { 840 // Clear invalid expression result so that the next evaluation 841 // will pick up the newly loaded aggregates. 842 ev.clearExpResultCache(false); 843 } 844 845 if (attempt++ > maxEvalDepth) { 846 throw Util.newInternal( 847 "Failed to load all aggregations after " + 848 maxEvalDepth + "passes; there's probably a cycle"); 849 } 850 } 851 852 // If there were pending reads when we entered, some of the other 853 // expressions may have been evaluated incorrectly. Set the reader's 854 // 'dirty' flag so that the caller knows that it must re-evaluate them. 855 if (dirty) { 856 batchingReader.setDirty(true); 857 } 858 859 RolapEvaluator ev = evaluator.push(); 860 ev.setCellReader(aggregatingReader); 861 return calc.evaluate(ev); 862 } 863 864 private void executeStripe( 865 int axisOrdinal, 866 RolapEvaluator revaluator, 867 final int[] pos) 868 { 869 if (axisOrdinal < 0) { 870 Axis axis = slicerAxis; 871 List<Position> positions = axis.getPositions(); 872 for (Position position : positions) { 873 getQuery().checkCancelOrTimeout(); 874 revaluator.setContext(position); 875 Object o; 876 try { 877 o = revaluator.evaluateCurrent(); 878 } catch (MondrianEvaluationException e) { 879 LOGGER.warn("Mondrian: exception in executeStripe.", e); 880 o = e; 881 } 882 883 CellInfo ci = null; 884 885 // Get the Cell's format string and value formatting 886 // Object. 887 try { 888 // This code is a combination of the code found in 889 // the old RolapResult 890 // <code>getCellNoDefaultFormatString</code> method and 891 // the old RolapCell <code>getFormattedValue</code> method. 892 893 // Create a CellInfo object for the given position 894 // integer array. 895 ci = cellInfos.create(point.getOrdinals()); 896 897 String cachedFormatString = null; 898 ValueFormatter valueFormatter; 899 900 // Determine if there is a CellFormatter registered for 901 // the current Cube's Measure's Dimension. If so, 902 // then find or create a CellFormatterValueFormatter 903 // for it. If not, then find or create a Locale based 904 // FormatValueFormatter. 905 final RolapCube cube = getCube(); 906 Dimension measuresDim = 907 cube.getMeasuresHierarchy().getDimension(); 908 RolapMeasure m = 909 (RolapMeasure) revaluator.getContext(measuresDim); 910 CellFormatter cf = m.getFormatter(); 911 if (cf != null) { 912 valueFormatter = cellFormatters.get(cf); 913 if (valueFormatter == null) { 914 valueFormatter = new CellFormatterValueFormatter(cf); 915 cellFormatters.put(cf, valueFormatter); 916 } 917 } else { 918 cachedFormatString = revaluator.getFormatString(); 919 Locale locale = query.getConnection().getLocale(); 920 valueFormatter = formatValueFormatters.get(locale); 921 if (valueFormatter == null) { 922 valueFormatter = new FormatValueFormatter(locale); 923 formatValueFormatters.put(locale, valueFormatter); 924 } 925 } 926 927 ci.formatString = cachedFormatString; 928 ci.valueFormatter = valueFormatter; 929 930 } catch (ResultLimitExceededException e) { 931 // Do NOT ignore a ResultLimitExceededException!!! 932 throw e; 933 934 } catch (MondrianEvaluationException e) { 935 // ignore but warn 936 LOGGER.warn("Mondrian: exception in executeStripe.", e); 937 } catch (Error e) { 938 // Errors indicate fatal JVM problems; do not discard 939 throw e; 940 } catch (Throwable e) { 941 LOGGER.warn("Mondrian: exception in executeStripe.", e); 942 Util.discard(e); 943 } 944 945 if (o == RolapUtil.valueNotReadyException) { 946 continue; 947 } 948 949 ci.value = o; 950 } 951 } else { 952 Axis axis = axes[axisOrdinal]; 953 List<Position> positions = axis.getPositions(); 954 if (positionsHighCardinality.get(axisOrdinal) == null) { 955 try { 956 positionsHighCardinality.put( 957 axisOrdinal, 958 positions.get(0).get(0).getDimension() 959 .isHighCardinality()); 960 } catch (IndexOutOfBoundsException ioobe) { 961 // No elements... no problem 962 } 963 } 964 if (positionsHighCardinality.get(axisOrdinal) != null 965 && positionsHighCardinality.get(axisOrdinal)) 966 { 967 final int limit = 968 MondrianProperties.instance().HighCardChunkSize.get(); 969 if (positionsIterators.get(axisOrdinal) == null) { 970 final Iterator<Position> it = positions.iterator(); 971 positionsIterators.put(axisOrdinal, it); 972 positionsIndexes.put(axisOrdinal, 0); 973 final List<Position> subPositions = 974 new ArrayList<Position>(); 975 for (int i = 0; i < limit && it.hasNext(); i++) { 976 subPositions.add(it.next()); 977 } 978 positionsCurrent.put(axisOrdinal, subPositions); 979 } 980 final Iterator<Position> it = 981 positionsIterators.get(axisOrdinal); 982 final int positionIndex = positionsIndexes.get(axisOrdinal); 983 List<Position> subPositions = positionsCurrent.get(axisOrdinal); 984 985 if (subPositions == null) { 986 return; 987 } 988 989 int pi; 990 if (pos[axisOrdinal] > positionIndex + subPositions.size() - 1 991 && subPositions.size() == limit) 992 { 993 pi = positionIndex + subPositions.size(); 994 positionsIndexes.put( 995 axisOrdinal, positionIndex + subPositions.size()); 996 subPositions.subList(0, subPositions.size()).clear(); 997 for (int i = 0; i < limit && it.hasNext(); i++) { 998 subPositions.add(it.next()); 999 } 1000 positionsCurrent.put(axisOrdinal, subPositions); 1001 } else { 1002 pi = positionIndex; 1003 } 1004 for (final Position position : subPositions) { 1005 point.setAxis(axisOrdinal, pi); 1006 revaluator.setContext(position); 1007 getQuery().checkCancelOrTimeout(); 1008 executeStripe(axisOrdinal - 1, revaluator, pos); 1009 pi++; 1010 } 1011 } else { 1012 int positionIndex = 0; 1013 for (final Position position : positions) { 1014 point.setAxis(axisOrdinal, positionIndex); 1015 revaluator.setContext(position); 1016 getQuery().checkCancelOrTimeout(); 1017 executeStripe(axisOrdinal - 1, revaluator, pos); 1018 positionIndex++; 1019 } 1020 } 1021 } 1022 } 1023 1024 /** 1025 * Converts a cell ordinal to a set of cell coordinates. Converse of 1026 * {@link #getCellOrdinal}. For example, if this result is 10 x 10 x 10, 1027 * then cell ordinal 537 has coordinates (5, 3, 7). 1028 * <p> 1029 * This method is no longer used. 1030 */ 1031 int[] getCellPos(int cellOrdinal) { 1032 if (modulos == null) { 1033 makeModulos(); 1034 } 1035 return modulos.getCellPos(cellOrdinal); 1036 } 1037 1038 /** 1039 * Converts a set of cell coordinates to a cell ordinal. Converse of 1040 * {@link #getCellPos}. 1041 * <p> 1042 * This method can be expensive, because the ordinal is computed from the 1043 * length of the axes, and therefore the axes need to be instantiated. 1044 */ 1045 int getCellOrdinal(int[] pos) { 1046 if (modulos == null) { 1047 makeModulos(); 1048 } 1049 return modulos.getCellOrdinal(pos); 1050 } 1051 1052 /* 1053 * Instantiates the calculator to convert cell coordinates to a cell ordinal 1054 * and vice versa. 1055 * 1056 * <p>To create the calculator, any axis that is based upon an Iterable is 1057 * converted into a List - thus increasing memory usage. 1058 */ 1059 protected void makeModulos() { 1060 modulos = Modulos.Generator.create(axes); 1061 } 1062 1063 /** 1064 * Called only by RolapCell. 1065 * 1066 * @param pos Coordinates of cell 1067 * @return Evaluator whose context is the given cell 1068 */ 1069 RolapEvaluator getCellEvaluator(int[] pos) { 1070 final RolapEvaluator cellEvaluator = evaluator.push(); 1071 for (int i = 0; i < pos.length; i++) { 1072 Position position = axes[i].getPositions().get(pos[i]); 1073 cellEvaluator.setContext(position); 1074 } 1075 return cellEvaluator; 1076 } 1077 1078 /** 1079 * Called only by RolapCell. Use this when creating an Evaluator 1080 * (using method {@link #getCellEvaluator}) is not required. 1081 * 1082 * @param pos Coordinates of cell 1083 * @return Members which form the context of the given cell 1084 */ 1085 Member[] getCellMembers(int[] pos) { 1086 Member[] members = evaluator.getMembers().clone(); 1087 final Cube cube = getCube(); 1088 for (int i = 0; i < pos.length; i++) { 1089 Position position = axes[i].getPositions().get(pos[i]); 1090 for (Member member : position) { 1091 RolapMember m = (RolapMember) member; 1092 int ordinal = m.getDimension().getOrdinal(cube); 1093 members[ordinal] = m; 1094 } 1095 1096 } 1097 return members; 1098 } 1099 1100 Evaluator getRootEvaluator() { 1101 return evaluator; 1102 } 1103 1104 Evaluator getEvaluator(int[] pos) { 1105 // Set up evaluator's context, so that context-dependent format 1106 // strings work properly. 1107 Evaluator cellEvaluator = evaluator.push(); 1108 for (int i = -1; i < axes.length; i++) { 1109 Axis axis; 1110 int index; 1111 if (i < 0) { 1112 axis = slicerAxis; 1113 index = 0; 1114 } else { 1115 axis = axes[i]; 1116 index = pos[i]; 1117 } 1118 Position position = axis.getPositions().get(index); 1119 cellEvaluator.setContext(position); 1120 } 1121 return cellEvaluator; 1122 } 1123 1124 1125 /** 1126 * Counts and collects Members found of the axes. 1127 * This class does two things. First it collects all Members 1128 * found during the Member-Determination phase. 1129 * Secondly, it counts how many Members are on each axis and 1130 * forms the product, the totalCellCount which is checked against 1131 * the ResultLimit property value. 1132 */ 1133 private static class AxisMember implements Iterable<Member> { 1134 private final List<Member> members; 1135 private final int limit; 1136 private boolean isSlicer; 1137 private int totalCellCount; 1138 private int axisCount; 1139 private boolean countOnly; 1140 1141 AxisMember() { 1142 this.countOnly = false; 1143 this.members = new ConcatenableList<Member>(); 1144 this.totalCellCount = 1; 1145 this.axisCount = 0; 1146 // Now that the axes are evaluated, make sure that the number of 1147 // cells does not exceed the result limit. 1148 this.limit = MondrianProperties.instance().ResultLimit.get(); 1149 } 1150 public Iterator<Member> iterator() { 1151 return members.iterator(); 1152 } 1153 void setSlicer(final boolean isSlicer) { 1154 this.isSlicer = isSlicer; 1155 } 1156 boolean isEmpty() { 1157 return this.members.isEmpty(); 1158 } 1159 void countOnly(boolean countOnly) { 1160 this.countOnly = countOnly; 1161 } 1162 void checkLimit() { 1163 if (this.limit > 0) { 1164 this.totalCellCount *= this.axisCount; 1165 if (this.totalCellCount > this.limit) { 1166 throw MondrianResource.instance(). 1167 TotalMembersLimitExceeded.ex(this.totalCellCount, 1168 this.limit); 1169 } 1170 this.axisCount = 0; 1171 } 1172 } 1173 void clearAxisCount() { 1174 this.axisCount = 0; 1175 } 1176 void clearTotalCellCount() { 1177 this.totalCellCount = 1; 1178 } 1179 void clearMembers() { 1180 this.members.clear(); 1181 this.axisCount = 0; 1182 this.totalCellCount = 1; 1183 } 1184 List<Member> members() { 1185 return this.members; 1186 } 1187 1188 void merge(List list) { 1189 if (!list.isEmpty()) { 1190 if (list.get(0) instanceof Member[]) { 1191 for (Member[] o : Util.<Member[]>cast(list)) { 1192 merge(o); 1193 } 1194 } else { 1195 for (Member o : Util.<Member>cast(list)) { 1196 if (o == null) { 1197 continue; 1198 } 1199 if (o.getDimension().isHighCardinality()) { 1200 break; 1201 } 1202 merge(o); 1203 } 1204 } 1205 } 1206 } 1207 1208 void merge(Iterator it) { 1209 if (it.hasNext()) { 1210 Object o = it.next(); 1211 if (o instanceof Member[]) { 1212 merge((Member[]) o); 1213 while (it.hasNext()) { 1214 o = it.next(); 1215 merge((Member[]) o); 1216 } 1217 } else { 1218 merge((Member) o); 1219 while (it.hasNext()) { 1220 o = it.next(); 1221 merge((Member) o); 1222 } 1223 } 1224 } 1225 } 1226 1227 private Member getTopParent(final Member m) { 1228 Member parent = m.getParentMember(); 1229 return (parent == null) ? m : getTopParent(parent); 1230 } 1231 1232 private void merge(final Member[] members) { 1233 for (Member member : members) { 1234 merge(member); 1235 } 1236 } 1237 1238 private void merge(final Member member) { 1239 this.axisCount++; 1240 if (! countOnly) { 1241 if (isSlicer) { 1242 if (! members.contains(member)) { 1243 members.add(member); 1244 } 1245 } else { 1246 if (member.isNull()) { 1247 return; 1248 } else if (member.isMeasure()) { 1249 return; 1250 } else if (member.isCalculated()) { 1251 return; 1252 } else if (member.isAll()) { 1253 return; 1254 } 1255 Member topParent = getTopParent(member); 1256 if (! this.members.contains(topParent)) { 1257 this.members.add(topParent); 1258 } 1259 } 1260 } 1261 } 1262 } 1263 1264 1265 /** 1266 * Extension to {@link RolapEvaluator.RolapEvaluatorRoot} which is capable 1267 * of evaluating named sets.<p/> 1268 * 1269 * A given set is only evaluated once each time a query is executed; the 1270 * result is added to the {@link #namedSetValues} cache on first execution 1271 * and re-used.<p/> 1272 * 1273 * Named sets are always evaluated in the context of the slicer.<p/> 1274 */ 1275 protected static class RolapResultEvaluatorRoot 1276 extends RolapEvaluator.RolapEvaluatorRoot { 1277 /** 1278 * Maps the names of sets to their values. Populated on demand. 1279 */ 1280 private final Map<String, Object> namedSetValues = 1281 new HashMap<String, Object>(); 1282 1283 /** 1284 * Evaluator containing context resulting from evaluating the slicer. 1285 */ 1286 private RolapEvaluator slicerEvaluator; 1287 private final RolapResult result; 1288 private static final Object Sentinel = new Object(); 1289 1290 public RolapResultEvaluatorRoot(RolapResult result) { 1291 super(result.query); 1292 this.result = result; 1293 } 1294 1295 protected void init(Evaluator evaluator) { 1296 slicerEvaluator = (RolapEvaluator) evaluator; 1297 } 1298 1299 protected Object evaluateNamedSet(String name, Exp exp) { 1300 Object value = namedSetValues.get(name); 1301 if (value == null) { 1302 final RolapEvaluator.RolapEvaluatorRoot root = 1303 slicerEvaluator.root; 1304 final Calc calc = root.getCompiled(exp, false, ResultStyle.LIST); 1305 Object o = result.evaluateExp(calc, slicerEvaluator.push()); 1306 List list; 1307 if (o instanceof List) { 1308 list = (List) o; 1309 } else { 1310 // Iterable 1311 1312 // TODO: 1313 // Here, we have to convert the Iterable into a List, 1314 // materialize it, because in the class 1315 // mondrian.mdx.NamedSetExpr the Calc returned by the 1316 // 'accept' method is an AbstractListCalc (hence we must 1317 // provide a list here). It would be better if the 1318 // NamedSetExpr class knew if it needed a ListCalc or 1319 // an IterCalc. 1320 Iterable iter = (Iterable) o; 1321 list = new ArrayList(); 1322 for (Object e : iter) { 1323 list.add(e); 1324 } 1325 } 1326 // Make immutable, just in case expressions are modifying the 1327 // results we give them. 1328 value = Collections.unmodifiableList(list); 1329 namedSetValues.put(name, value); 1330 } 1331 return value; 1332 } 1333 1334 public Object getParameterValue(ParameterSlot slot) { 1335 Object value = slot.getParameterValue(); 1336 if (value != null) { 1337 return value; 1338 } 1339 1340 // Look in other places for the value. Which places we look depends 1341 // on the scope of the parameter. 1342 Parameter.Scope scope = slot.getParameter().getScope(); 1343 switch (scope) { 1344 case System: 1345 // TODO: implement system params 1346 1347 // fall through 1348 case Schema: 1349 // TODO: implement schema params 1350 1351 // fall through 1352 case Connection: 1353 // if it's set in the session, return that value 1354 1355 // fall through 1356 case Statement: 1357 break; 1358 1359 default: 1360 throw Util.badValue(scope); 1361 } 1362 1363 // Not set in any accessible scope. Evaluate the default value, 1364 // then cache it. 1365 value = slot.getCachedDefaultValue(); 1366 if (value != null) { 1367 if (value == Sentinel) { 1368 throw MondrianResource.instance(). 1369 CycleDuringParameterEvaluation.ex( 1370 slot.getParameter().getName()); 1371 } 1372 return value; 1373 } 1374 // Set value to a sentinel, so we can detect cyclic evaluation. 1375 slot.setCachedDefaultValue(Sentinel); 1376 value = result.evaluateExp( 1377 slot.getDefaultValueCalc(), slicerEvaluator.push()); 1378 slot.setCachedDefaultValue(value); 1379 return value; 1380 } 1381 } 1382 1383 /** 1384 * Formatter to convert values into formatted strings. 1385 * 1386 * <p>Every Cell has a value, a format string (or CellFormatter) and a 1387 * formatted value string. 1388 * There are a wide range of possible values (pick a Double, any 1389 * Double - its a value). Because there are lots of possible values, 1390 * there are also lots of possible formatted value strings. On the 1391 * other hand, there are only a very small number of format strings 1392 * and CellFormatter's. These formatters are to be cached 1393 * in a synchronized HashMaps in order to limit how many copies 1394 * need to be kept around. 1395 * 1396 * <p> 1397 * There are two implementations of the ValueFormatter interface:<ul> 1398 * <li>{@link CellFormatterValueFormatter}, which formats using a 1399 * user-registered {@link CellFormatter}; and 1400 * <li> {@link FormatValueFormatter}, which takes the {@link Locale} object. 1401 * </ul> 1402 */ 1403 interface ValueFormatter { 1404 String format(Object value, String formatString); 1405 } 1406 1407 /** 1408 * A CellFormatterValueFormatter uses a user-defined {@link CellFormatter} 1409 * to format values. 1410 */ 1411 static class CellFormatterValueFormatter implements ValueFormatter { 1412 final CellFormatter cf; 1413 1414 /** 1415 * Creates a CellFormatterValueFormatter 1416 * 1417 * @param cf Cell formatter 1418 */ 1419 CellFormatterValueFormatter(CellFormatter cf) { 1420 this.cf = cf; 1421 } 1422 public String format(Object value, String formatString) { 1423 return cf.formatCell(value); 1424 } 1425 } 1426 1427 /** 1428 * A FormatValueFormatter takes a {@link Locale} 1429 * as a parameter and uses it to get the {@link mondrian.util.Format} 1430 * to be used in formatting an Object value with a 1431 * given format string. 1432 */ 1433 static class FormatValueFormatter implements ValueFormatter { 1434 final Locale locale; 1435 1436 /** 1437 * Creates a FormatValueFormatter. 1438 * 1439 * @param locale Locale 1440 */ 1441 FormatValueFormatter(Locale locale) { 1442 this.locale = locale; 1443 } 1444 public String format(Object value, String formatString) { 1445 if (value == Util.nullValue) { 1446 Format format = getFormat(formatString); 1447 return format.format(null); 1448 } else if (value instanceof Throwable) { 1449 return "#ERR: " + value.toString(); 1450 } else if (value instanceof String) { 1451 return (String) value; 1452 } else { 1453 Format format = getFormat(formatString); 1454 return format.format(value); 1455 } 1456 } 1457 private Format getFormat(String formatString) { 1458 return Format.get(formatString, locale); 1459 } 1460 } 1461 1462 /* 1463 * Generate a long ordinal based upon the values of the integers 1464 * stored in the cell position array. With this mechanism, the 1465 * Cell information can be stored using a long key (rather than 1466 * the array integer of positions) thus saving memory. The trick 1467 * is to use a 'large number' per axis in order to convert from 1468 * position array to long key where the 'large number' is greater 1469 * than the number of members in the axis. 1470 * The largest 'long' is java.lang.Long.MAX_VALUE which is 1471 * 9,223,372,036,854,776,000. The product of the maximum number 1472 * of members per axis must be less than this maximum 'long' 1473 * value (otherwise one gets hashing collisions). 1474 * <p> 1475 * For a single axis, the maximum number of members is equal to 1476 * the max 'long' number, 9,223,372,036,854,776,000. 1477 * <p> 1478 * For two axes, the maximum number of members is the square root 1479 * of the max 'long' number, 9,223,372,036,854,776,000, which is 1480 * slightly bigger than 2,147,483,647 (which is the maximum integer). 1481 * <p> 1482 * For three axes, the maximum number of members per axis is the 1483 * cube root of the max 'long' which is about 2,000,000 1484 * <p> 1485 * For four axes the forth root is about 50,000. 1486 * <p> 1487 * For five or more axes, the maximum number of members per axis 1488 * based upon the root of the maximum 'long' number, 1489 * start getting too small to guarantee that it will be 1490 * smaller than the number of members on a given axis and so 1491 * we must resort to the Map-base Cell container. 1492 */ 1493 1494 1495 1496 /** 1497 * Synchronized Map from Locale to ValueFormatter. It is expected that 1498 * there will be only a small number of Locale's. 1499 * Should these be a WeakHashMap? 1500 */ 1501 protected static final Map<Locale, ValueFormatter> 1502 formatValueFormatters = 1503 Collections.synchronizedMap(new HashMap<Locale, ValueFormatter>()); 1504 1505 /** 1506 * Synchronized Map from CellFormatter to ValueFormatter. 1507 * CellFormatter's are defined in schema files. It is expected 1508 * the there will only be a small number of CellFormatter's. 1509 * Should these be a WeakHashMap? 1510 */ 1511 protected static final Map<CellFormatter, ValueFormatter> 1512 cellFormatters = 1513 Collections.synchronizedMap(new HashMap<CellFormatter, ValueFormatter>()); 1514 1515 /** 1516 * A CellInfo contains all of the information that a Cell requires. 1517 * It is placed in the cellInfos map during evaluation and 1518 * serves as a constructor parameter for {@link RolapCell}. 1519 * 1520 * <p>During the evaluation stage they are mutable but after evaluation has 1521 * finished they are not changed. 1522 */ 1523 static class CellInfo { 1524 Object value; 1525 String formatString; 1526 ValueFormatter valueFormatter; 1527 long key; 1528 1529 /** 1530 * Creates a CellInfo representing the position of a cell. 1531 * 1532 * @param key Ordinal representing the position of a cell 1533 */ 1534 CellInfo(long key) { 1535 this(key, null, null, null); 1536 } 1537 1538 /** 1539 * Creates a CellInfo with position, value, format string and formatter 1540 * of a cell. 1541 * 1542 * @param key Ordinal representing the position of a cell 1543 * @param value Value of cell, or null if not yet known 1544 * @param formatString Format string of cell, or null 1545 * @param valueFormatter Formatter for cell, or null 1546 */ 1547 CellInfo( 1548 long key, 1549 Object value, 1550 String formatString, 1551 ValueFormatter valueFormatter) 1552 { 1553 this.key = key; 1554 this.value = value; 1555 this.formatString = formatString; 1556 this.valueFormatter = valueFormatter; 1557 } 1558 1559 public int hashCode() { 1560 // Combine the upper 32 bits of the key with the lower 32 bits. 1561 // We used to use 'key ^ (key >>> 32)' but that was bad, because 1562 // CellKey.Two encodes (i, j) as 1563 // (i * Integer.MAX_VALUE + j), which is practically the same as 1564 // (i << 32, j). If i and j were 1565 // both k bits long, all of the hashcodes were k bits long too! 1566 return (int) (key ^ (key >>> 11) ^ (key >>> 24)); 1567 } 1568 1569 public boolean equals(Object o) { 1570 if (o instanceof CellInfo) { 1571 CellInfo that = (CellInfo) o; 1572 return that.key == this.key; 1573 } else { 1574 return false; 1575 } 1576 } 1577 1578 /** 1579 * Returns the formatted value of the Cell 1580 * @return formatted value of the Cell 1581 */ 1582 String getFormatValue() { 1583 return valueFormatter.format(value, formatString); 1584 } 1585 } 1586 1587 /** 1588 * API for the creation and 1589 * lookup of {@link CellInfo} objects. There are two implementations, 1590 * one that uses a Map for storage and the other uses an ObjectPool. 1591 */ 1592 interface CellInfoContainer { 1593 /** 1594 * Returns the number of CellInfo objects in this container. 1595 * @return the number of CellInfo objects. 1596 */ 1597 int size(); 1598 /** 1599 * Reduces the size of the internal data structures needed to 1600 * support the current entries. This should be called after 1601 * all CellInfo objects have been added to container. 1602 */ 1603 void trimToSize(); 1604 /** 1605 * Removes all CellInfo objects from container. Does not 1606 * change the size of the internal data structures. 1607 */ 1608 void clear(); 1609 /** 1610 * Creates a new CellInfo object, adds it to the container 1611 * a location <code>pos</code> and returns it. 1612 * 1613 * @param pos where to store CellInfo object. 1614 * @return the newly create CellInfo object. 1615 */ 1616 CellInfo create(int[] pos); 1617 /** 1618 * Gets the CellInfo object at the location <code>pos</code>. 1619 * 1620 * @param pos where to find the CellInfo object. 1621 * @return the CellInfo found or null. 1622 */ 1623 CellInfo lookup(int[] pos); 1624 } 1625 1626 /** 1627 * Implementation of {@link CellInfoContainer} which uses a {@link Map} to 1628 * store CellInfo Objects. 1629 * 1630 * <p>Note that the CellKey point instance variable is the same 1631 * Object (NOT a copy) that is used and modified during 1632 * the recursive calls to executeStripe - the 1633 * <code>create</code> method relies on this fact. 1634 */ 1635 static class CellInfoMap implements CellInfoContainer { 1636 private final Map<CellKey, CellInfo> cellInfoMap; 1637 private final CellKey point; 1638 1639 /** 1640 * Creates a CellInfoMap 1641 * 1642 * @param point Cell position 1643 */ 1644 CellInfoMap(CellKey point) { 1645 this.point = point; 1646 this.cellInfoMap = new HashMap<CellKey, CellInfo>(); 1647 } 1648 public int size() { 1649 return this.cellInfoMap.size(); 1650 } 1651 public void trimToSize() { 1652 // empty 1653 } 1654 public void clear() { 1655 this.cellInfoMap.clear(); 1656 } 1657 public CellInfo create(int[] pos) { 1658 CellKey key = this.point.copy(); 1659 CellInfo ci = this.cellInfoMap.get(key); 1660 if (ci == null) { 1661 ci = new CellInfo(0); 1662 this.cellInfoMap.put(key, ci); 1663 } 1664 return ci; 1665 } 1666 public CellInfo lookup(int[] pos) { 1667 CellKey key = CellKey.Generator.newCellKey(pos); 1668 return this.cellInfoMap.get(key); 1669 } 1670 } 1671 1672 /** 1673 * Implementation of {@link CellInfoContainer} which uses an 1674 * {@link ObjectPool} to store {@link CellInfo} Objects. 1675 * 1676 * <p>There is an inner interface (<code>CellKeyMaker</code>) and 1677 * implementations for 0 through 4 axes that convert the Cell 1678 * position integer array into a long. 1679 * 1680 * <p> 1681 * It should be noted that there is an alternate approach. 1682 * As the <code>executeStripe</code> 1683 * method is recursively called, at each call it is known which 1684 * axis is being iterated across and it is known whether or 1685 * not the Position object for that axis is a List or just 1686 * an Iterable. It it is a List, then one knows the real 1687 * size of the axis. If it is an Iterable, then one has to 1688 * use one of the MAX_AXIS_SIZE values. Given that this information 1689 * is available when one recursives down to the next 1690 * <code>executeStripe</code> call, the Cell ordinal, the position 1691 * integer array could converted to an <code>long</code>, could 1692 * be generated on the call stack!! Just a thought for the future. 1693 */ 1694 static class CellInfoPool implements CellInfoContainer { 1695 /** 1696 * The maximum number of Members, 2,147,483,647, that can be any given 1697 * Axis when the number of Axes is 2. 1698 */ 1699 protected static final long MAX_AXIS_SIZE_2 = 2147483647; 1700 /** 1701 * The maximum number of Members, 2,000,000, that can be any given 1702 * Axis when the number of Axes is 3. 1703 */ 1704 protected static final long MAX_AXIS_SIZE_3 = 2000000; 1705 /** 1706 * The maximum number of Members, 50,000, that can be any given 1707 * Axis when the number of Axes is 4. 1708 */ 1709 protected static final long MAX_AXIS_SIZE_4 = 50000; 1710 1711 /** 1712 * Implementations of CellKeyMaker convert the Cell 1713 * position integer array to a <code>long</code>. 1714 */ 1715 interface CellKeyMaker { 1716 long generate(int[] pos); 1717 } 1718 /** 1719 * For axis of size 0. 1720 */ 1721 static class Zero implements CellKeyMaker { 1722 public long generate(int[] pos) { 1723 return 0; 1724 } 1725 } 1726 /** 1727 * For axis of size 1. 1728 */ 1729 static class One implements CellKeyMaker { 1730 public long generate(int[] pos) { 1731 return pos[0]; 1732 } 1733 } 1734 /** 1735 * For axis of size 2. 1736 */ 1737 static class Two implements CellKeyMaker { 1738 public long generate(int[] pos) { 1739 long l = pos[0]; 1740 l += (MAX_AXIS_SIZE_2 * (long) pos[1]); 1741 return l; 1742 } 1743 } 1744 /** 1745 * For axis of size 3. 1746 */ 1747 static class Three implements CellKeyMaker { 1748 public long generate(int[] pos) { 1749 long l = pos[0]; 1750 l += (MAX_AXIS_SIZE_3 * (long) pos[1]); 1751 l += (MAX_AXIS_SIZE_3 * MAX_AXIS_SIZE_3 * (long) pos[2]); 1752 return l; 1753 } 1754 } 1755 /** 1756 * For axis of size 4. 1757 */ 1758 static class Four implements CellKeyMaker { 1759 public long generate(int[] pos) { 1760 long l = pos[0]; 1761 l += (MAX_AXIS_SIZE_4 * (long) pos[1]); 1762 l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * (long) pos[2]); 1763 l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * (long) pos[3]); 1764 return l; 1765 } 1766 } 1767 1768 private final ObjectPool<CellInfo> cellInfoPool; 1769 private final CellKeyMaker cellKeyMaker; 1770 1771 CellInfoPool(int axisLength) { 1772 this.cellInfoPool = new ObjectPool<CellInfo>(); 1773 this.cellKeyMaker = createCellKeyMaker(axisLength); 1774 } 1775 1776 CellInfoPool(int axisLength, int initialSize) { 1777 this.cellInfoPool = new ObjectPool<CellInfo>(initialSize); 1778 this.cellKeyMaker = createCellKeyMaker(axisLength); 1779 } 1780 1781 private static CellKeyMaker createCellKeyMaker(int axisLength) { 1782 switch (axisLength) { 1783 case 0: 1784 return new Zero(); 1785 case 1: 1786 return new One(); 1787 case 2: 1788 return new Two(); 1789 case 3: 1790 return new Three(); 1791 case 4: 1792 return new Four(); 1793 default: 1794 throw new RuntimeException( 1795 "Creating CellInfoPool with axisLength=" + axisLength); 1796 } 1797 } 1798 1799 public int size() { 1800 return this.cellInfoPool.size(); 1801 } 1802 public void trimToSize() { 1803 this.cellInfoPool.trimToSize(); 1804 } 1805 public void clear() { 1806 this.cellInfoPool.clear(); 1807 } 1808 public CellInfo create(int[] pos) { 1809 long key = this.cellKeyMaker.generate(pos); 1810 return this.cellInfoPool.add(new CellInfo(key)); 1811 } 1812 public CellInfo lookup(int[] pos) { 1813 long key = this.cellKeyMaker.generate(pos); 1814 return this.cellInfoPool.add(new CellInfo(key)); 1815 } 1816 } 1817 1818 static Axis mergeAxes(Axis axis1, Axis axis2, RolapEvaluator evaluator, 1819 boolean ordered) { 1820 if (axis1 == null) { 1821 return axis2; 1822 } 1823 List<Position> pl1 = axis1.getPositions(); 1824 List<Position> pl2 = axis2.getPositions(); 1825 int arrayLen = -1; 1826 if (pl1 instanceof RolapAxis.PositionListBase) { 1827 if (pl1.isEmpty()) { 1828 return axis2; 1829 } 1830 arrayLen = pl1.get(0).size(); 1831 } 1832 if (axis1 instanceof RolapAxis.SingleEmptyPosition) { 1833 return axis2; 1834 } 1835 if (axis1 instanceof RolapAxis.NoPosition) { 1836 return axis2; 1837 } 1838 if (pl2 instanceof RolapAxis.PositionListBase) { 1839 if (pl2.isEmpty()) { 1840 return axis1; 1841 } 1842 arrayLen = pl2.get(0).size(); 1843 } 1844 if (axis2 instanceof RolapAxis.SingleEmptyPosition) { 1845 return axis1; 1846 } 1847 if (axis2 instanceof RolapAxis.NoPosition) { 1848 return axis1; 1849 } 1850 if (arrayLen == -1) { 1851 // Avoid materialization of axis 1852 arrayLen = 0; 1853 for (Position p1 : pl1) { 1854 for (Member m1 : p1) { 1855 arrayLen++; 1856 } 1857 break; 1858 } 1859 // reset to start of List 1860 pl1 = axis1.getPositions(); 1861 } 1862 if (arrayLen == 1) { 1863 // single Member per position 1864 List<Member> list = new ArrayList<Member>(); 1865 for (Position p1 : pl1) { 1866 for (Member m1 : p1) { 1867 list.add(m1); 1868 } 1869 } 1870 for (Position p2 : pl2) { 1871 for (Member m2 : p2) { 1872 if (! list.contains(m2)) { 1873 list.add(m2); 1874 } 1875 } 1876 } 1877 return new RolapAxis.MemberList(list); 1878 } else { 1879 // array of Members per position 1880 List<Member[]> list = new ArrayList<Member[]>(); 1881 for (Position p1 : pl1) { 1882 Member[] members = new Member[arrayLen]; 1883 int i = 0; 1884 for (Member m1 : p1) { 1885 members[i++] = m1; 1886 } 1887 list.add(members); 1888 } 1889 List<Member[]> extras = new ArrayList<Member[]>(); 1890 for (Position p2 : pl2) { 1891 int i = 0; 1892 Member[] members = new Member[arrayLen]; 1893 for (Member m2 : p2) { 1894 members[i++] = m2; 1895 } 1896 Iterator<Member[]> it1 = list.iterator(); 1897 boolean found = false; 1898 while (it1.hasNext()) { 1899 Member[] m1 = it1.next(); 1900 if (java.util.Arrays.equals(members, m1)) { 1901 found = true; 1902 break; 1903 } 1904 } 1905 if (! found) { 1906 extras.add(members); 1907 } 1908 } 1909 list.addAll(extras); 1910 1911 // if there are unique members on both axes and no order function, 1912 // sort the list to ensure default order 1913 if (!list.isEmpty() && !extras.isEmpty() && ordered == false) { 1914 Member[] membs = list.get(0); 1915 int membsSize = membs.length; 1916 ValueCalc valCalc = new ValueCalc(new DummyExp(new ScalarType())); 1917 FunUtil.sortTuples(evaluator, list, valCalc, 1918 false, false, membsSize); 1919 } 1920 1921 return new RolapAxis.MemberArrayList(list); 1922 } 1923 } 1924 } 1925 1926 // End RolapResult.java