001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapResult.java#4 $
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-2007 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 mondrian.olap.*;
016 import mondrian.olap.fun.MondrianEvaluationException;
017 import mondrian.resource.MondrianResource;
018 import mondrian.rolap.agg.AggregationManager;
019 import mondrian.util.Format;
020 import mondrian.util.ObjectPool;
021
022 import mondrian.olap.fun.FunUtil;
023 import mondrian.olap.type.ScalarType;
024 import mondrian.calc.*;
025 import mondrian.calc.impl.ValueCalc;
026
027 import org.apache.log4j.Logger;
028
029 import java.util.Collections;
030 import java.util.ArrayList;
031 import java.util.List;
032 import java.util.Iterator;
033 import java.util.ListIterator;
034 import java.util.HashMap;
035 import java.util.Map;
036 import java.util.Locale;
037
038 /**
039 * A <code>RolapResult</code> is the result of running a query.
040 *
041 * @author jhyde
042 * @since 10 August, 2001
043 * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapResult.java#4 $
044 */
045 public
046 class RolapResult extends ResultBase {
047
048 private static final Logger LOGGER = Logger.getLogger(ResultBase.class);
049
050 private RolapEvaluator evaluator;
051 private final CellKey point;
052
053 private CellInfoContainer cellInfos;
054 private FastBatchingCellReader batchingReader;
055 private final CellReader aggregatingReader =
056 AggregationManager.instance().getCacheCellReader();
057 private Modulos modulos = null;
058 private final int maxEvalDepth =
059 MondrianProperties.instance().MaxEvalDepth.get();
060
061 RolapResult(Query query, boolean execute) {
062 super(query, new Axis[query.axes.length]);
063
064 this.point = CellKey.Generator.newCellKey(query.axes.length);
065 final int expDeps = MondrianProperties.instance().TestExpDependencies.get();
066 if (expDeps > 0) {
067 this.evaluator = new RolapDependencyTestingEvaluator(this, expDeps);
068 } else {
069 final RolapEvaluator.RolapEvaluatorRoot root =
070 new RolapResultEvaluatorRoot(this);
071 this.evaluator = new RolapEvaluator(root);
072 }
073 RolapCube rcube = (RolapCube) query.getCube();
074 this.batchingReader = new FastBatchingCellReader(rcube);
075
076 this.cellInfos = (query.axes.length > 4)
077 ? new CellInfoMap(point) : new CellInfoPool(query.axes.length);
078
079
080 if (!execute) {
081 return;
082 }
083
084 boolean normalExecution = true;
085 try {
086 // This call to clear the cube's cache only has an
087 // effect if caching has been disabled, otherwise
088 // nothing happens.
089 // Clear the local cache before a query has run
090 rcube.clearCachedAggregations();
091 // Check if there are modifications to the global aggregate cache
092 rcube.checkAggregateModifications();
093
094
095 /////////////////////////////////////////////////////////////////
096 //
097 // Evaluation Algorithm
098 //
099 // There are three basic steps to the evaluation algorithm:
100 // 1) Determine all Members for each axis but do not save
101 // information (do not build the RolapAxis),
102 // 2) Save all Members for each axis (build RolapAxis).
103 // 3) Evaluate and store each Cell determined by the Members
104 // of the axes.
105 // Step 1 converges on the stable set of Members pre axis.
106 // Steps 1 and 2 make sure that the data has been loaded.
107 //
108 // More detail follows.
109 //
110 // Explicit and Implicit Members:
111 // A Member is said to be 'explicit' if it appears on one of
112 // the Axes (one of the RolapAxis Position List of Members).
113 // A Member is 'implicit' if it is in the query but does not
114 // end up on any Axes (its usage, for example, is in a function).
115 // When for a Dimension none of its Members are explicit in the
116 // query, then the default Member is used which is like putting
117 // the Member in the Slicer.
118 //
119 // Special Dimensions:
120 // There are 2 special dimensions.
121 // The first is the Time dimension. If in a schema there is
122 // no ALL Member, then Whatever happens to be the default
123 // Member is used if Time Members are not explicitly set
124 // in the query.
125 // The second is the Measures dimension. This dimension
126 // NEVER has an ALL Member. A cube's default Measure is set
127 // by convention - its simply the first Measure defined in the
128 // cube.
129 //
130 // First a RolapEvaluator is created. During its creation,
131 // it gets a Member from each Hierarchy. Each Member is the
132 // default Member of the Hierarchy. For most Hierarchies this
133 // Member is the ALL Member, but there are cases where 1)
134 // a Hierarchy does not have an ALL Member or 2) the Hierarchy
135 // has an ALL Member but that Member is not the default Member.
136 // In these cases, the default Member is still used, but its
137 // use can cause evaluation issues (seemingly strange evaluation
138 // results).
139 //
140 // Next, load all root Members for Hierarchies that have no ALL
141 // Member and load ALL Members that are not the default Member.
142 //
143 // Determine the Members of the Slicer axis (Step 1 above). Any
144 // Members found are added to the AxisMember object. If one of these
145 // Members happens to be a Measure, then the Slicer is explicitly
146 // specifying the query's Measure and this should be put into the
147 // evaluator's context (replacing the default Measure which just
148 // happens to be the first Measure defined in the cube). Other
149 // Members found in the AxisMember object are also placed into the
150 // evaluator's context since these also are explicitly specified.
151 // Also, any other Members in the AxisMember object which have the
152 // same Hierarchy as Members in the list of root Members for
153 // Hierarchies that have no ALL Member, replace those Members - they
154 // Slicer has explicitly determined which ones to use. The
155 // AxisMember object is now cleared.
156 // The Slicer does not depend upon the other Axes, but the other
157 // Axes depend upon both the Slicer and each other.
158 //
159 // The AxisMember object also checks if the number of Members
160 // exceeds the ResultLimit property throwing a
161 // TotalMembersLimitExceeded Exception if it does.
162 //
163 // For all non-Slicer axes, the Members are determined (Step 1
164 // above). If a Measure is found in the AxisMember, then an
165 // Axis is explicitly specifying a Measure.
166 // If any Members in the AxisMember object have the same Hierarchy
167 // as a Member in the set of root Members for Hierarchies that have
168 // no ALL Member, then replace those root Members with the Member
169 // from the AxisMember object. In this case, again, a Member
170 // was explicitly specified in an Axis. If this replacement
171 // occurs, then one must redo this step with the new Members.
172 //
173 // Now Step 3 above is done. First to the Slicer Axis and then
174 // to the other Axes. Here the Axes are actually generated.
175 // If a Member of an Axis is an Calculated Member (and the
176 // Calculated Member is not a Member of the Measure Hierarchy),
177 // then find the Dimension associated with the Calculated
178 // Member and remove Members with the same Dimension in the set of
179 // root Members for Hierarchies that have no ALL Member.
180 // This is done because via the Calculated Member the Member
181 // was implicitly specified in the query. If this removal occurs,
182 // then the Axes must be re-evaluated repeating Step 3.
183 //
184 /////////////////////////////////////////////////////////////////
185
186
187 // The AxisMember object is used to hold Members that are found
188 // during Step 1 when the Axes are determined.
189 final AxisMember axisMembers = new AxisMember();
190
191
192 // list of ALL Members that are not default Members
193 final List<Member> nonDefaultAllMembers = new ArrayList<Member>();
194
195 // List of Members of Hierarchies that do not have an ALL Member
196 List<Member[]> nonAllMembers = new ArrayList<Member[]>();
197
198 // List of Measures
199 final List<Member> measureMembers = new ArrayList<Member>();
200
201 // load all root Members for Hierarchies that have no ALL
202 // Member and load ALL Members that are not the default Member.
203 // Also, all Measures are are gathered.
204 loadSpecialMembers(nonDefaultAllMembers,
205 nonAllMembers, measureMembers);
206
207 // clear evaluation cache
208 query.clearEvalCache();
209
210 // Save, may be needed by some Expression Calc's
211 query.putEvalCache("ALL_MEMBER_LIST", nonDefaultAllMembers);
212
213
214 final List<Member[]> emptyNonAllMembers = Collections.emptyList();
215
216 /////////////////////////////////////////////////////////////////
217 //
218 // Determine Slicer
219 //
220 axisMembers.setSlicer(true);
221 loadMembers(emptyNonAllMembers, evaluator,
222 query.slicerAxis, query.slicerCalc, axisMembers);
223 axisMembers.setSlicer(false);
224
225 if (! axisMembers.isEmpty()) {
226 for (Member m : axisMembers) {
227 evaluator.setSlicerContext(m);
228 if (m.isMeasure()) {
229 // A Measure was explicitly declared in the
230 // Slicer, don't need to worry about Measures
231 // for this query.
232 measureMembers.clear();
233 }
234 }
235 replaceNonAllMembers(nonAllMembers, axisMembers);
236 axisMembers.clearMembers();
237 }
238 //
239 /////////////////////////////////////////////////////////////////
240
241
242 /////////////////////////////////////////////////////////////////
243 //
244 // Determine Axes
245 //
246 boolean changed = false;
247
248 // reset to total member count
249 axisMembers.clearTotalCellCount();
250
251 for (int i = 0; i < axes.length; i++) {
252 final QueryAxis axis = query.axes[i];
253 final Calc calc = query.axisCalcs[i];
254 loadMembers(emptyNonAllMembers, evaluator,
255 axis, calc, axisMembers);
256 }
257
258 if (! axisMembers.isEmpty()) {
259 for (Member m : axisMembers) {
260 if (m.isMeasure()) {
261 // A Measure was explicitly declared on an
262 // axis, don't need to worry about Measures
263 // for this query.
264 measureMembers.clear();
265 }
266 }
267 changed = replaceNonAllMembers(nonAllMembers, axisMembers);
268 axisMembers.clearMembers();
269 }
270
271 /*
272 // This code allows replacing default Measure Member
273 // with one found in the query, an implicit Measure.
274 // Fixes some problems (RolapResultTest._testNullDefaultMeasure)
275 // but causes other ones (NamedSetTest.testNamedSetUsedInCrossJoin)
276 // so it can not be used.
277 Member previous = null;
278 if (! measureMembers.isEmpty()) {
279 MeasureVisitor visitor = new MeasureVisitor();
280 for (int i = 0; i < axes.length; i++) {
281 query.axes[i].accept(visitor);
282 }
283 println("Measures on Axis:");
284 for (Member m : visitor.measures) {
285 println(" m=" +m.getUniqueName());
286 }
287 if (! visitor.measures.isEmpty()) {
288 Member m = visitor.measures.get(0);
289 previous = evaluator.setContext(m);
290 changed |= ! m.equals(previous);
291 }
292 }
293 // move this
294 // it should be set just before calling executeBody
295 // evaluator.setContext(previous);
296 */
297
298
299 if (changed) {
300 // only count number of members, do not collect any
301 axisMembers.countOnly(true);
302 // reset to total member count
303 axisMembers.clearTotalCellCount();
304
305 for (int i = 0; i < axes.length; i++) {
306 final QueryAxis axis = query.axes[i];
307 final Calc calc = query.axisCalcs[i];
308 loadMembers(
309 nonAllMembers,
310 evaluator.push(),
311 axis, calc, axisMembers);
312 }
313 }
314
315 // throws exception if number of members exceeds limit
316 axisMembers.checkLimit();
317
318 //
319 /////////////////////////////////////////////////////////////////
320
321 /////////////////////////////////////////////////////////////////
322 //
323 // Execute Slicer
324 //
325 this.slicerAxis =
326 evalExecute(
327 nonAllMembers,
328 nonAllMembers.size() - 1,
329 evaluator.push(),
330 query.slicerAxis,
331 query.slicerCalc);
332
333 // Use the context created by the slicer for the other
334 // axes. For example, "select filter([Customers], [Store
335 // Sales] > 100) on columns from Sales where
336 // ([Time].[1998])" should show customers whose 1998 (not
337 // total) purchases exceeded 100.
338
339 // Getting the Position list's size and the Position
340 // at index == 0 will, in fact, cause an Iterable-base
341 // Axis Position List to become a List-base Axis
342 // Position List (and increae memory usage), but for
343 // the slicer axis, the number of Positions is very
344 // small, so who cares.
345 switch (this.slicerAxis.getPositions().size()) {
346 case 0:
347 throw MondrianResource.instance().EmptySlicer.ex();
348 case 1:
349 break;
350 default:
351 throw MondrianResource.instance().CompoundSlicer.ex();
352 }
353 //
354 /////////////////////////////////////////////////////////////////
355
356 /////////////////////////////////////////////////////////////////
357 //
358 // Execute Axes
359 //
360 boolean redo = true;
361 while (redo) {
362 RolapEvaluator e = evaluator.push();
363 redo = false;
364
365 for (int i = 0; i < axes.length; i++) {
366 QueryAxis axis = query.axes[i];
367 final Calc calc = query.axisCalcs[i];
368 Axis axisResult = evalExecute(nonAllMembers,
369 nonAllMembers.size()-1, e, axis, calc);
370
371 if (! nonAllMembers.isEmpty()) {
372 List<Position> pl = axisResult.getPositions();
373 if (pl.size() > 0) {
374 // Only need to process the first Position
375 Position p = pl.get(0);
376 for (Member m : p) {
377 if (m.isCalculated()) {
378 CalculatedMeasureVisitor visitor =
379 new CalculatedMeasureVisitor();
380 m.getExpression().accept(visitor);
381 Dimension dimension = visitor.dimension;
382 redo = removeDimension(dimension, nonAllMembers);
383 }
384 }
385 }
386 }
387 this.axes[i] = axisResult;
388 }
389 }
390 //
391 /////////////////////////////////////////////////////////////////
392
393 /////////////////////////////////////////////////////////////////
394 //
395 // Get value for each Cell
396 //
397 executeBody(query);
398 //
399 /////////////////////////////////////////////////////////////////
400
401 // If you are very close to running out of memory due to
402 // the number of CellInfo's in cellInfos, then calling this
403 // may cause the out of memory one is trying to aviod.
404 // On the other hand, calling this can reduce the size of
405 // the ObjectPool's internal storage by half (but, of course,
406 // it will not reduce the size of the stored objects themselves).
407 // Only call this if there are lots of CellInfo.
408 if (this.cellInfos.size() > 10000) {
409 this.cellInfos.trimToSize();
410 }
411
412 } catch (ResultLimitExceededException ex) {
413 // If one gets a ResultLimitExceededException, then
414 // don't count on anything being worth caching.
415 normalExecution = false;
416
417 // De-reference data structures that might be holding
418 // partial results but surely are taking up memory.
419 evaluator = null;
420 cellInfos = null;
421 batchingReader = null;
422 for (int i = 0; i < axes.length; i++) {
423 axes[i] = null;
424 }
425 slicerAxis = null;
426
427 query.clearEvalCache();
428
429 throw ex;
430
431 } finally {
432 if (normalExecution) {
433 // Push all modifications to the aggregate cache to the global
434 // cache so each thread can start using it
435 rcube.pushAggregateModificationsToGlobalCache();
436
437 // Expression cache duration is for each query. It is time to
438 // clear out the whole expression cache at the end of a query.
439 evaluator.clearExpResultCache(true);
440 }
441 if (LOGGER.isDebugEnabled()) {
442 LOGGER.debug("RolapResult<init>: " + Util.printMemory());
443 }
444 }
445 }
446 protected boolean removeDimension(Dimension dimension, List<Member[]> nonAllMembers) {
447 boolean changed = false;
448 for (ListIterator<Member[]> it = nonAllMembers.listIterator();
449 it.hasNext();) {
450 Member[] ms = it.next();
451 Dimension d = ms[0].getHierarchy().getDimension();
452 if (d.equals(dimension)) {
453 it.remove();
454 }
455 }
456 return changed;
457 }
458
459 private static class CalculatedMeasureVisitor extends mondrian.mdx.MdxVisitorImpl {
460 Dimension dimension;
461 CalculatedMeasureVisitor() {
462 }
463 public Object visit(mondrian.olap.Formula formula) {
464 return null;
465 }
466 public Object visit(mondrian.mdx.ResolvedFunCall call) {
467 return null;
468 }
469 public Object visit(mondrian.olap.Id id) {
470 return null;
471 }
472 public Object visit(mondrian.mdx.ParameterExpr parameterExpr) {
473 return null;
474 }
475 public Object visit(mondrian.mdx.DimensionExpr dimensionExpr) {
476 dimension = dimensionExpr.getDimension();
477 return null;
478 }
479 public Object visit(mondrian.mdx.HierarchyExpr hierarchyExpr) {
480 Hierarchy hierarchy = hierarchyExpr.getHierarchy();
481 dimension = hierarchy.getDimension();
482 return null;
483 }
484 public Object visit(mondrian.mdx.LevelExpr levelExpr) {
485 return null;
486 }
487 public Object visit(mondrian.mdx.MemberExpr memberExpr) {
488 Member member = memberExpr.getMember();
489 dimension = member.getHierarchy().getDimension();
490 return null;
491 }
492 public Object visit(mondrian.mdx.NamedSetExpr namedSetExpr) {
493 return null;
494 }
495 public Object visit(mondrian.olap.Literal literal) {
496 return null;
497 }
498 }
499
500 protected boolean replaceNonAllMembers(List<Member[]> nonAllMembers,
501 AxisMember axisMembers) {
502
503 boolean changed = false;
504 List<Member> mList = new ArrayList<Member>();
505 for (ListIterator<Member[]> it = nonAllMembers.listIterator();
506 it.hasNext();) {
507 Member[] ms = it.next();
508 Hierarchy h = ms[0].getHierarchy();
509 mList.clear();
510 for (Member m : axisMembers) {
511 if (m.getHierarchy().equals(h)) {
512 mList.add(m);
513 }
514 }
515 if (! mList.isEmpty()) {
516 changed = true;
517 Member[] ml = mList.toArray(new Member[mList.size()]);
518 it.set(ml);
519 }
520 }
521 return changed;
522
523 }
524
525 protected void loadMembers(List<Member[]> nonAllMembers,
526 RolapEvaluator evaluator, QueryAxis axis, Calc calc,
527 AxisMember axisMembers) {
528 int attempt = 0;
529 evaluator.setCellReader(batchingReader);
530 while (true) {
531 axisMembers.clearAxisCount();
532 evalLoad(
533 nonAllMembers, nonAllMembers.size() - 1,
534 evaluator, axis, calc, axisMembers);
535
536 if (!batchingReader.loadAggregations(query)) {
537 break;
538 } else {
539 // Clear invalid expression result so that the next evaluation
540 // will pick up the newly loaded aggregates.
541 evaluator.clearExpResultCache(false);
542 }
543
544 if (attempt++ > maxEvalDepth) {
545 throw Util.newInternal(
546 "Failed to load all aggregations after " +
547 maxEvalDepth +
548 " passes; there's probably a cycle");
549 }
550 }
551 }
552
553 void evalLoad(List<Member[]> nonAllMembers, int cnt,
554 Evaluator evaluator, QueryAxis axis, Calc calc,
555 AxisMember axisMembers) {
556 if (cnt < 0) {
557 executeAxis(evaluator.push(), axis, calc, false, axisMembers);
558 } else {
559 for (Member m : nonAllMembers.get(cnt)) {
560 evaluator.setContext(m);
561 evalLoad(nonAllMembers, cnt-1, evaluator, axis, calc, axisMembers);
562 }
563 }
564 }
565 Axis evalExecute(List<Member[]> nonAllMembers, int cnt,
566 RolapEvaluator evaluator, QueryAxis axis, Calc calc) {
567 Axis axisResult = null;
568 if (cnt < 0) {
569 evaluator.setCellReader(aggregatingReader);
570 axisResult =
571 executeAxis(evaluator.push(), axis, calc, true, null);
572 // No need to clear expression cache here as no new aggregates are
573 // loaded(aggregatingReader reads from cache).
574 } else {
575 for (Member m : nonAllMembers.get(cnt)) {
576 evaluator.setContext(m);
577 Axis a = evalExecute(nonAllMembers, cnt-1, evaluator, axis, calc);
578 boolean ordered = false;
579 if (axis != null) {
580 ordered = axis.isOrdered();
581 }
582 axisResult = mergeAxes(axisResult, a, evaluator, ordered);
583 }
584 }
585 return axisResult;
586 }
587
588 /**
589 * Finds all root Members 1) whose Hierarchy does not have an ALL
590 * Member, 2) whose default Member is not the ALL Member and 3)
591 * all Measures.
592 *
593 * @param nonDefaultAllMembers List of all root Members for Hierarchies
594 * whose default Member is not the ALL Member.
595 * @param nonAllMembers List of root Members for Hierarchies that have no
596 * ALL Member.
597 * @param measureMembers List all Measures
598 */
599 protected void loadSpecialMembers(
600 List<Member> nonDefaultAllMembers,
601 List<Member[]> nonAllMembers,
602 List<Member> measureMembers)
603 {
604 SchemaReader schemaReader = evaluator.getSchemaReader();
605 Member[] evalMembers = evaluator.getMembers();
606 for (Member em : evalMembers) {
607 if (em.isCalculated()) {
608 continue;
609 }
610 Hierarchy h = em.getHierarchy();
611 Dimension d = h.getDimension();
612 if (d.getDimensionType() == DimensionType.TimeDimension) {
613 continue;
614 }
615 if (!em.isAll()) {
616 Member[] rootMembers = schemaReader.getHierarchyRootMembers(h);
617 if (em.isMeasure()) {
618 for (Member mm : rootMembers) {
619 measureMembers.add(mm);
620 }
621 } else {
622 if (h.hasAll()) {
623 for (Member m : rootMembers) {
624 if (m.isAll()) {
625 nonDefaultAllMembers.add(m);
626 break;
627 }
628 }
629 } else {
630 nonAllMembers.add(rootMembers);
631 }
632 }
633 }
634 }
635 }
636
637 protected Logger getLogger() {
638 return LOGGER;
639 }
640
641 public final RolapCube getCube() {
642 return evaluator.getCube();
643 }
644
645 // implement Result
646 public Axis[] getAxes() {
647 return axes;
648 }
649
650 /**
651 * Get the Cell for the given Cell position.
652 *
653 * @param pos Cell position.
654 * @return the Cell associated with the Cell position.
655 */
656 public Cell getCell(int[] pos) {
657 if (pos.length != point.size()) {
658 throw Util.newError(
659 "coordinates should have dimension " + point.size());
660 }
661
662 CellInfo ci = cellInfos.lookup(pos);
663 if (ci.value == null) {
664 for (int i = 0; i < pos.length; i++) {
665 int po = pos[i];
666 if (po < 0 || po >= axes[i].getPositions().size()) {
667 throw Util.newError("coordinates out of range");
668 }
669 }
670 ci.value = Util.nullValue;
671 }
672
673 return new RolapCell(this, pos.clone(), ci);
674 }
675
676 private Axis executeAxis(
677 Evaluator evaluator,
678 QueryAxis axis,
679 Calc axisCalc,
680 boolean construct,
681 AxisMember axisMembers)
682 {
683 Axis axisResult = null;
684 if (axis == null) {
685 // Create an axis containing one position with no members (not
686 // the same as an empty axis).
687 if (construct) {
688 axisResult = new RolapAxis.SingleEmptyPosition();
689 }
690
691 } else {
692 evaluator.setNonEmpty(axis.isNonEmpty());
693 evaluator.setEvalAxes(true);
694 Object value = axisCalc.evaluate(evaluator);
695 if (axisCalc.getClass().getName().indexOf("OrderFunDef") != -1) {
696 axis.setOrdered(true);
697 }
698 evaluator.setNonEmpty(false);
699 if (value != null) {
700 // List or Iterable of Member or Member[]
701 if (value instanceof List) {
702 List<Object> list = (List) value;
703 if (construct) {
704 if (list.size() == 0) {
705 // should be???
706 axisResult = new RolapAxis.NoPosition();
707 } else if (list.get(0) instanceof Member[]) {
708 axisResult =
709 new RolapAxis.MemberArrayList((List<Member[]>)value);
710 } else {
711 axisResult =
712 new RolapAxis.MemberList((List<Member>)value);
713 }
714 } else if (axisMembers != null) {
715 axisMembers.merge(value);
716 }
717 } else {
718 // Iterable
719 Iterable<Object> iter = (Iterable) value;
720 Iterator it = iter.iterator();
721 if (construct) {
722 if (! it.hasNext()) {
723 axisResult = new RolapAxis.NoPosition();
724 } else if (it.next() instanceof Member[]) {
725 axisResult = new RolapAxis.MemberArrayIterable(
726 (Iterable<Member[]>)value);
727 } else {
728 axisResult = new RolapAxis.MemberIterable(
729 (Iterable<Member>)value);
730 }
731 } else if (axisMembers != null) {
732 axisMembers.merge(it);
733 }
734 }
735 }
736 evaluator.setEvalAxes(false);
737 }
738 return axisResult;
739 }
740
741 private void executeBody(Query query) {
742 // Compute the cells several times. The first time, use a dummy
743 // evaluator which collects requests.
744 int count = 0;
745 while (true) {
746
747 evaluator.setCellReader(batchingReader);
748 executeStripe(query.axes.length - 1, evaluator.push());
749
750 // Retrieve the aggregations collected.
751 //
752 if (!batchingReader.loadAggregations(query)) {
753 // We got all of the cells we needed, so the result must be
754 // correct.
755 return;
756 } else {
757 // Clear invalid expression result so that the next evaluation
758 // will pick up the newly loaded aggregates.
759 evaluator.clearExpResultCache(false);
760 }
761
762 if (count++ > maxEvalDepth) {
763 if (evaluator instanceof RolapDependencyTestingEvaluator) {
764 // The dependency testing evaluator can trigger new
765 // requests every cycle. So let is run as normal for
766 // the first N times, then run it disabled.
767 ((RolapDependencyTestingEvaluator.DteRoot)
768 evaluator.root).disabled = true;
769 if (count > maxEvalDepth * 2) {
770 throw Util.newInternal("Query required more than "
771 + count + " iterations");
772 }
773 } else {
774 throw Util.newInternal("Query required more than "
775 + count + " iterations");
776 }
777 }
778
779 cellInfos.clear();
780 }
781 }
782
783 boolean isDirty() {
784 return batchingReader.isDirty();
785 }
786
787 private Object evaluateExp(Calc calc, RolapEvaluator evaluator) {
788 int attempt = 0;
789 boolean dirty = batchingReader.isDirty();
790 while (true) {
791 RolapEvaluator ev = evaluator.push();
792
793 ev.setCellReader(batchingReader);
794 Object preliminaryValue = calc.evaluate(ev);
795 Util.discard(preliminaryValue);
796
797 if (!batchingReader.loadAggregations(evaluator.getQuery())) {
798 break;
799 } else {
800 // Clear invalid expression result so that the next evaluation
801 // will pick up the newly loaded aggregates.
802 ev.clearExpResultCache(false);
803 }
804
805 if (attempt++ > maxEvalDepth) {
806 throw Util.newInternal(
807 "Failed to load all aggregations after " +
808 maxEvalDepth + "passes; there's probably a cycle");
809 }
810 }
811
812 // If there were pending reads when we entered, some of the other
813 // expressions may have been evaluated incorrectly. Set the reader's
814 // 'dirty' flag so that the caller knows that it must re-evaluate them.
815 if (dirty) {
816 batchingReader.setDirty(true);
817 }
818
819 RolapEvaluator ev = evaluator.push();
820 ev.setCellReader(aggregatingReader);
821 return calc.evaluate(ev);
822 }
823
824 private void executeStripe(int axisOrdinal, RolapEvaluator revaluator) {
825 if (axisOrdinal < 0) {
826 Axis axis = slicerAxis;
827 List<Position> positions = axis.getPositions();
828 for (Position position: positions) {
829 getQuery().checkCancelOrTimeout();
830 revaluator.setContext(position);
831 Object o;
832 try {
833 o = revaluator.evaluateCurrent();
834 } catch (MondrianEvaluationException e) {
835 LOGGER.warn("Mondrian: exception in executeStripe.", e);
836 o = e;
837 }
838
839 CellInfo ci = null;
840
841 // Get the Cell's format string and value formatting
842 // Object.
843 try {
844 // This code is a combination of the code found in
845 // the old RolapResult
846 // <code>getCellNoDefaultFormatString</code> method and
847 // the old RolapCell <code>getFormattedValue</code> method.
848
849 // Create a CellInfo object for the given position
850 // integer array.
851 ci = cellInfos.create(point.getOrdinals());
852
853 String cachedFormatString = null;
854 ValueFormatter valueFormatter;
855
856 // Determine if there is a CellFormatter registered for
857 // the current Cube's Measure's Dimension. If so,
858 // then find or create a CellFormatterValueFormatter
859 // for it. If not, then find or create a Locale based
860 // FormatValueFormatter.
861 final RolapCube cube = getCube();
862 Dimension measuresDim =
863 cube.getMeasuresHierarchy().getDimension();
864 RolapMeasure m =
865 (RolapMeasure) revaluator.getContext(measuresDim);
866 CellFormatter cf = m.getFormatter();
867 if (cf != null) {
868 valueFormatter = cellFormatters.get(cf);
869 if (valueFormatter == null) {
870 valueFormatter = new CellFormatterValueFormatter(cf);
871 cellFormatters.put(cf, valueFormatter);
872 }
873 } else {
874 cachedFormatString = revaluator.getFormatString();
875 Locale locale = query.getConnection().getLocale();
876 valueFormatter = formatValueFormatters.get(locale);
877 if (valueFormatter == null) {
878 valueFormatter = new FormatValueFormatter(locale);
879 formatValueFormatters.put(locale, valueFormatter);
880 }
881 }
882
883 ci.formatString = cachedFormatString;
884 ci.valueFormatter = valueFormatter;
885
886 } catch (ResultLimitExceededException e) {
887 // Do NOT ignore a ResultLimitExceededException!!!
888 throw e;
889
890 } catch (MondrianEvaluationException e) {
891 // ignore but warn
892 LOGGER.warn("Mondrian: exception in executeStripe.", e);
893 } catch (Error e) {
894 // Errors indicate fatal JVM problems; do not discard
895 throw e;
896 } catch (Throwable e) {
897 LOGGER.warn("Mondrian: exception in executeStripe.", e);
898 Util.discard(e);
899 }
900
901 if (o == RolapUtil.valueNotReadyException) {
902 continue;
903 }
904
905 ci.value = o;
906 }
907 } else {
908 Axis axis = axes[axisOrdinal];
909 List<Position> positions = axis.getPositions();
910 int i = 0;
911 for (Position position: positions) {
912 point.setAxis(axisOrdinal, i);
913 revaluator.setContext(position);
914 getQuery().checkCancelOrTimeout();
915 executeStripe(axisOrdinal - 1, revaluator);
916 i++;
917 }
918 }
919 }
920
921 /**
922 * Converts a cell ordinal to a set of cell coordinates. Converse of
923 * {@link #getCellOrdinal}. For example, if this result is 10 x 10 x 10,
924 * then cell ordinal 537 has coordinates (5, 3, 7).
925 * <p>
926 * This method is no longer used.
927 */
928 int[] getCellPos(int cellOrdinal) {
929 if (modulos == null) {
930 makeModulos();
931 }
932 return modulos.getCellPos(cellOrdinal);
933 }
934
935 /**
936 * Converts a set of cell coordinates to a cell ordinal. Converse of
937 * {@link #getCellPos}.
938 * <p>
939 * This method can be expensive, because the ordinal is computed from the
940 * length of the axes, and therefore the axes need to be instantiated.
941 */
942 int getCellOrdinal(int[] pos) {
943 if (modulos == null) {
944 makeModulos();
945 }
946 return modulos.getCellOrdinal(pos);
947 }
948
949 /*
950 * Instantiates the calculator to convert cell coordinates to a cell ordinal
951 * and vice versa.
952 *
953 * <p>To create the calculator, any axis that is based upon an Iterable is
954 * converted into a List - thus increasing memory usage.
955 */
956 protected void makeModulos() {
957 modulos = Modulos.Generator.create(axes);
958 }
959
960 /**
961 * Called only by RolapCell.
962 *
963 * @param pos Coordinates of cell
964 * @return Evaluator whose context is the given cell
965 */
966 RolapEvaluator getCellEvaluator(int[] pos) {
967 final RolapEvaluator cellEvaluator = evaluator.push();
968 for (int i = 0; i < pos.length; i++) {
969 Position position = axes[i].getPositions().get(pos[i]);
970 cellEvaluator.setContext(position);
971 }
972 return cellEvaluator;
973 }
974
975 /**
976 * Called only by RolapCell. Use this when creating an Evaluator
977 * (using method {@link #getCellEvaluator}) is not required.
978 *
979 * @param pos Coordinates of cell
980 * @return Members which form the context of the given cell
981 */
982 Member[] getCellMembers(int[] pos) {
983 Member[] members = evaluator.getMembers().clone();
984 final Cube cube = getCube();
985 for (int i = 0; i < pos.length; i++) {
986 Position position = axes[i].getPositions().get(pos[i]);
987 for (Member member: position) {
988 RolapMember m = (RolapMember) member;
989 int ordinal = m.getDimension().getOrdinal(cube);
990 members[ordinal] = m;
991 }
992
993 }
994 return members;
995 }
996
997 Evaluator getRootEvaluator() {
998 return evaluator;
999 }
1000
1001 Evaluator getEvaluator(int[] pos) {
1002 // Set up evaluator's context, so that context-dependent format
1003 // strings work properly.
1004 Evaluator cellEvaluator = evaluator.push();
1005 for (int i = -1; i < axes.length; i++) {
1006 Axis axis;
1007 int index;
1008 if (i < 0) {
1009 axis = slicerAxis;
1010 index = 0;
1011 } else {
1012 axis = axes[i];
1013 index = pos[i];
1014 }
1015 Position position = axis.getPositions().get(index);
1016 cellEvaluator.setContext(position);
1017 }
1018 return cellEvaluator;
1019 }
1020
1021
1022 /**
1023 * Counts and collects Members found of the axes.
1024 * This class does two things. First it collects all Members
1025 * found during the Member-Determination phase.
1026 * Secondly, it counts how many Members are on each axis and
1027 * forms the product, the totalCellCount which is checked against
1028 * the ResultLimit property value.
1029 */
1030 private static class AxisMember implements Iterable<Member> {
1031 private final List<Member> members;
1032 private final int limit;
1033 private boolean isSlicer;
1034 private int totalCellCount;
1035 private int axisCount;
1036 private boolean countOnly;
1037
1038 AxisMember() {
1039 this.countOnly = false;
1040 this.members = new ArrayList<Member>();
1041 this.totalCellCount = 1;
1042 this.axisCount = 0;
1043 // Now that the axes are evaluated, make sure that the number of
1044 // cells does not exceed the result limit.
1045 this.limit = MondrianProperties.instance().ResultLimit.get();
1046 }
1047 public Iterator<Member> iterator() {
1048 return members.iterator();
1049 }
1050 void setSlicer(final boolean isSlicer) {
1051 this.isSlicer = isSlicer;
1052 }
1053 boolean isEmpty() {
1054 return this.members.isEmpty();
1055 }
1056 void countOnly(boolean countOnly) {
1057 this.countOnly = countOnly;
1058 }
1059 void checkLimit() {
1060 if (this.limit > 0) {
1061 this.totalCellCount *= this.axisCount;
1062 if (this.totalCellCount > this.limit) {
1063 throw MondrianResource.instance().
1064 TotalMembersLimitExceeded.ex(this.totalCellCount,
1065 this.limit);
1066 }
1067 this.axisCount = 0;
1068 }
1069 }
1070 void clearAxisCount() {
1071 this.axisCount = 0;
1072 }
1073 void clearTotalCellCount() {
1074 this.totalCellCount = 1;
1075 }
1076 void clearMembers() {
1077 this.members.clear();
1078 this.axisCount = 0;
1079 this.totalCellCount = 1;
1080 }
1081 List<Member> members() {
1082 return this.members;
1083 }
1084 void merge(Object value) {
1085 List<Object> list = (List) value;
1086 if (list.size() != 0) {
1087 if (list.get(0) instanceof Member[]) {
1088 for (Member[] o : (List<Member[]>) value) {
1089 merge(o);
1090 }
1091 } else {
1092 for (Member o : (List<Member>) value) {
1093 merge(o);
1094 }
1095 }
1096 }
1097 }
1098 void merge(Iterator it) {
1099 if (it.hasNext()) {
1100 Object o = it.next();
1101 if (o instanceof Member[]) {
1102 merge((Member[]) o);
1103 while (it.hasNext()) {
1104 o = it.next();
1105 merge((Member[]) o);
1106 }
1107 } else {
1108 merge((Member) o);
1109 while (it.hasNext()) {
1110 o = it.next();
1111 merge((Member) o);
1112 }
1113 }
1114 }
1115 }
1116 private Member getTopParent(final Member m) {
1117 Member parent = m.getParentMember();
1118 return (parent == null) ? m : getTopParent(parent);
1119 }
1120
1121 private void merge(final Member[] members) {
1122 for (Member member : members) {
1123 merge(member);
1124 }
1125 }
1126
1127 private void merge(final Member member) {
1128 this.axisCount++;
1129 if (! countOnly) {
1130 if (isSlicer) {
1131 if (! members.contains(member)) {
1132 members.add(member);
1133 }
1134 } else {
1135 if (member.isNull()) {
1136 return;
1137 } else if (member.isMeasure()) {
1138 return;
1139 } else if (member.isCalculated()) {
1140 return;
1141 } else if (member.isAll()) {
1142 return;
1143 }
1144 Member topParent = getTopParent(member);
1145 if (! this.members.contains(topParent)) {
1146 this.members.add(topParent);
1147 }
1148 }
1149 }
1150 }
1151 }
1152
1153
1154 /**
1155 * Extension to {@link RolapEvaluator.RolapEvaluatorRoot} which is capable
1156 * of evaluating named sets.<p/>
1157 *
1158 * A given set is only evaluated once each time a query is executed; the
1159 * result is added to the {@link #namedSetValues} cache on first execution
1160 * and re-used.<p/>
1161 *
1162 * Named sets are always evaluated in the context of the slicer.<p/>
1163 */
1164 protected static class RolapResultEvaluatorRoot
1165 extends RolapEvaluator.RolapEvaluatorRoot {
1166 /**
1167 * Maps the names of sets to their values. Populated on demand.
1168 */
1169 private final Map<String, Object> namedSetValues =
1170 new HashMap<String, Object>();
1171
1172 /**
1173 * Evaluator containing context resulting from evaluating the slicer.
1174 */
1175 private RolapEvaluator slicerEvaluator;
1176 private final RolapResult result;
1177 private static final Object Sentinel = new Object();
1178
1179 public RolapResultEvaluatorRoot(RolapResult result) {
1180 super(result.query);
1181 this.result = result;
1182 }
1183
1184 protected void init(Evaluator evaluator) {
1185 slicerEvaluator = (RolapEvaluator) evaluator;
1186 }
1187
1188 protected Object evaluateNamedSet(String name, Exp exp) {
1189 Object value = namedSetValues.get(name);
1190 if (value == null) {
1191 final RolapEvaluator.RolapEvaluatorRoot root =
1192 slicerEvaluator.root;
1193 final Calc calc = root.getCompiled(exp, false, ResultStyle.LIST);
1194 Object o = result.evaluateExp(calc, slicerEvaluator.push());
1195 List list;
1196 if (o instanceof List) {
1197 list = (List) o;
1198 } else {
1199 // Iterable
1200
1201 // TODO:
1202 // Here, we have to convert the Iterable into a List,
1203 // materialize it, because in the class
1204 // mondrian.mdx.NamedSetExpr the Calc returned by the
1205 // 'accept' method is an AbstractListCalc (hence we must
1206 // provide a list here). It would be better if the
1207 // NamedSetExpr class knew if it needed a ListCalc or
1208 // an IterCalc.
1209 Iterable iter = (Iterable) o;
1210 list = new ArrayList();
1211 for (Object e : iter) {
1212 list.add(e);
1213 }
1214 }
1215 // Make immutable, just in case expressions are modifying the
1216 // results we give them.
1217 value = Collections.unmodifiableList(list);
1218 namedSetValues.put(name, value);
1219 }
1220 return value;
1221 }
1222
1223 public Object getParameterValue(ParameterSlot slot) {
1224 Object value = slot.getParameterValue();
1225 if (value != null) {
1226 return value;
1227 }
1228
1229 // Look in other places for the value. Which places we look depends
1230 // on the scope of the parameter.
1231 Parameter.Scope scope = slot.getParameter().getScope();
1232 switch (scope) {
1233 case System:
1234 // TODO: implement system params
1235
1236 // fall through
1237 case Schema:
1238 // TODO: implement schema params
1239
1240 // fall through
1241 case Connection:
1242 // if it's set in the session, return that value
1243
1244 // fall through
1245 case Statement:
1246 break;
1247
1248 default:
1249 throw Util.badValue(scope);
1250 }
1251
1252 // Not set in any accessible scope. Evaluate the default value,
1253 // then cache it.
1254 value = slot.getCachedDefaultValue();
1255 if (value != null) {
1256 if (value == Sentinel) {
1257 throw MondrianResource.instance().
1258 CycleDuringParameterEvaluation.ex(
1259 slot.getParameter().getName());
1260 }
1261 return value;
1262 }
1263 // Set value to a sentinel, so we can detect cyclic evaluation.
1264 slot.setCachedDefaultValue(Sentinel);
1265 value = result.evaluateExp(
1266 slot.getDefaultValueCalc(), slicerEvaluator.push());
1267 slot.setCachedDefaultValue(value);
1268 return value;
1269 }
1270 }
1271
1272 /**
1273 * Formatter to convert values into formatted strings.
1274 *
1275 * <p>Every Cell has a value, a format string (or CellFormatter) and a
1276 * formatted value string.
1277 * There are a wide range of possible values (pick a Double, any
1278 * Double - its a value). Because there are lots of possible values,
1279 * there are also lots of possible formatted value strings. On the
1280 * other hand, there are only a very small number of format strings
1281 * and CellFormatter's. These formatters are to be cached
1282 * in a synchronized HashMaps in order to limit how many copies
1283 * need to be kept around.
1284 *
1285 * <p>
1286 * There are two implementations of the ValueFormatter interface:<ul>
1287 * <li>{@link CellFormatterValueFormatter}, which formats using a
1288 * user-registered {@link CellFormatter}; and
1289 * <li> {@link FormatValueFormatter}, which takes the {@link Locale} object.
1290 * </ul>
1291 */
1292 interface ValueFormatter {
1293 String format(Object value, String formatString);
1294 }
1295
1296 /**
1297 * A CellFormatterValueFormatter uses a user-defined {@link CellFormatter}
1298 * to format values.
1299 */
1300 static class CellFormatterValueFormatter implements ValueFormatter {
1301 final CellFormatter cf;
1302
1303 /**
1304 * Creates a CellFormatterValueFormatter
1305 *
1306 * @param cf Cell formatter
1307 */
1308 CellFormatterValueFormatter(CellFormatter cf) {
1309 this.cf = cf;
1310 }
1311 public String format(Object value, String formatString) {
1312 return cf.formatCell(value);
1313 }
1314 }
1315
1316 /**
1317 * A FormatValueFormatter takes a {@link Locale}
1318 * as a parameter and uses it to get the {@link mondrian.util.Format}
1319 * to be used in formatting an Object value with a
1320 * given format string.
1321 */
1322 static class FormatValueFormatter implements ValueFormatter {
1323 final Locale locale;
1324
1325 /**
1326 * Creates a FormatValueFormatter.
1327 *
1328 * @param locale Locale
1329 */
1330 FormatValueFormatter(Locale locale) {
1331 this.locale = locale;
1332 }
1333 public String format(Object value, String formatString) {
1334 if (value == Util.nullValue) {
1335 Format format = getFormat(formatString);
1336 return format.format(null);
1337 } else if (value instanceof Throwable) {
1338 return "#ERR: " + value.toString();
1339 } else if (value instanceof String) {
1340 return (String) value;
1341 } else {
1342 Format format = getFormat(formatString);
1343 return format.format(value);
1344 }
1345 }
1346 private Format getFormat(String formatString) {
1347 return Format.get(formatString, locale);
1348 }
1349 }
1350
1351 /*
1352 * Generate a long ordinal based upon the values of the integers
1353 * stored in the cell position array. With this mechanism, the
1354 * Cell information can be stored using a long key (rather than
1355 * the array integer of positions) thus saving memory. The trick