001 /*
002 // $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapCubeLevel.java#3 $
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 // wgorman, 19 October 2007
012 */
013 package mondrian.rolap;
014
015 import mondrian.olap.*;
016 import mondrian.rolap.agg.CellRequest;
017 import mondrian.rolap.agg.MemberColumnPredicate;
018 import mondrian.rolap.agg.MemberTuplePredicate;
019 import mondrian.rolap.agg.RangeColumnPredicate;
020 import mondrian.rolap.agg.ValueColumnPredicate;
021
022 /**
023 * RolapCubeLevel wraps a RolapLevel for a specific Cube.
024 *
025 * @author Will Gorman (wgorman@pentaho.org)
026 * @version $Id: //open/mondrian-release/3.0/src/main/mondrian/rolap/RolapCubeLevel.java#3 $
027 */
028 public class RolapCubeLevel extends RolapLevel {
029
030 private final RolapLevel rolapLevel;
031 private RolapStar.Column starKeyColumn = null;
032
033 protected LevelReader levelReader;
034
035 public RolapCubeLevel(RolapLevel level, RolapCubeHierarchy hierarchy) {
036 super(hierarchy, level.getDepth(), level.getName(), level.getKeyExp(),
037 level.getNameExp(), level.getCaptionExp(),
038 level.getOrdinalExp(), level.getParentExp(),
039 level.getNullParentValue(), null, level.getProperties(),
040 level.getFlags(), level.getDatatype(),
041 level.getHideMemberCondition(),
042 level.getLevelType(), "" + level.getApproxRowCount());
043
044 this.rolapLevel = level;
045 MondrianDef.RelationOrJoin hierarchyRel = hierarchy.getRelation();
046 keyExp = convertExpression(level.getKeyExp(), hierarchyRel);
047 nameExp = convertExpression(level.getNameExp(), hierarchyRel);
048 captionExp = convertExpression(level.getCaptionExp(), hierarchyRel);
049 ordinalExp = convertExpression(level.getOrdinalExp(), hierarchyRel);
050 parentExp = convertExpression(level.getParentExp(), hierarchyRel);
051 }
052
053 void init(MondrianDef.CubeDimension xmlDimension) {
054 if (isAll()) {
055 this.levelReader = new AllLevelReaderImpl();
056 } else if (getLevelType() == LevelType.Null) {
057 this.levelReader = new NullLevelReader();
058 } else if (rolapLevel.xmlClosure != null) {
059 RolapDimension dimension =
060 (RolapDimension)rolapLevel.getClosedPeer()
061 .getHierarchy().getDimension();
062
063 RolapCubeDimension cubeDimension =
064 new RolapCubeDimension(
065 getCube(), dimension, xmlDimension,
066 getDimension().getName() + "$Closure",
067 getHierarchy().getDimension().getOrdinal());
068
069 /*
070 RME HACK
071 WG: Note that the reason for registering this usage is so that
072 when registerDimension is called, the hierarchy is registered
073 successfully to the star. This type of hack will go away once
074 HierarchyUsage is phased out
075 */
076 getCube().createUsage(
077 (RolapCubeHierarchy)cubeDimension.getHierarchies()[0],
078 xmlDimension);
079
080 cubeDimension.init(xmlDimension);
081 getCube().registerDimension(cubeDimension);
082 RolapCubeLevel closedPeer =
083 (RolapCubeLevel) cubeDimension.getHierarchies()[0].getLevels()[1];
084
085 this.levelReader = new ParentChildLevelReaderImpl(closedPeer);
086 } else {
087 this.levelReader = new RegularLevelReader();
088 }
089 }
090
091 /**
092 * Converts an expression to new aliases if necessary.
093 *
094 * @param exp the expression to convert
095 * @param rel the parent relation
096 * @return returns the converted expression
097 */
098 private MondrianDef.Expression convertExpression(
099 MondrianDef.Expression exp,
100 MondrianDef.RelationOrJoin rel)
101 {
102 if (getHierarchy().isUsingCubeFact()) {
103 // no conversion necessary
104 return exp;
105 } else if (exp == null || rel == null) {
106 return null;
107 } else if (exp instanceof MondrianDef.Column) {
108 MondrianDef.Column col = (MondrianDef.Column)exp;
109 if (rel instanceof MondrianDef.Table) {
110 return new MondrianDef.Column(
111 ((MondrianDef.Table) rel).getAlias(),
112 col.getColumnName());
113 } else if (rel instanceof MondrianDef.Join
114 || rel instanceof MondrianDef.Relation) {
115 // need to determine correct name of alias for this level.
116 // this may be defined in level
117 // col.table
118 String alias = getHierarchy().lookupAlias(col.getTableAlias());
119 return new MondrianDef.Column(alias, col.getColumnName());
120 }
121 } else if (exp instanceof MondrianDef.ExpressionView) {
122 // this is a limitation, in the future, we may need
123 // to replace the table name in the sql provided
124 // with the new aliased name
125 return exp;
126 }
127 throw new RuntimeException("conversion of Class "+ exp.getClass() +
128 " unsupported at this time");
129 }
130
131 public void setStarKeyColumn(RolapStar.Column column) {
132 starKeyColumn = column;
133 }
134
135 /**
136 * This is the RolapStar.Column that is related to this RolapCubeLevel
137 *
138 * @return the RolapStar.Column related to this RolapCubeLevel
139 */
140 public RolapStar.Column getStarKeyColumn() {
141 return starKeyColumn;
142 }
143
144 LevelReader getLevelReader() {
145 return levelReader;
146 }
147
148 /**
149 * this method returns the RolapStar.Column if non-virtual,
150 * if virtual, find the base cube level and return it's
151 * column
152 *
153 * @param baseCube the base cube for the specificed virtual level
154 * @return the RolapStar.Column related to this RolapCubeLevel
155 */
156 public RolapStar.Column getBaseStarKeyColumn(RolapCube baseCube) {
157 RolapStar.Column column = null;
158 if (getCube().isVirtual()) {
159 RolapCubeLevel lvl = baseCube.findBaseCubeLevel(this);
160 if (lvl != null) {
161 column = lvl.getStarKeyColumn();
162 }
163 } else {
164 column = getStarKeyColumn();
165 }
166 return column;
167 }
168
169 /**
170 * Returns the (non virtual) cube this level belongs to.
171 *
172 * @return cube
173 */
174 public RolapCube getCube() {
175 return getHierarchy().getDimension().getCube();
176 }
177
178 // override with stricter return type
179 public final RolapCubeHierarchy getHierarchy() {
180 return (RolapCubeHierarchy) super.getHierarchy();
181 }
182
183 // override with stricter return type
184 public final RolapCubeLevel getChildLevel() {
185 return (RolapCubeLevel) super.getChildLevel();
186 }
187
188 // override with stricter return type
189 public RolapCubeLevel getParentLevel() {
190 return (RolapCubeLevel) super.getParentLevel();
191 }
192
193 public RolapLevel getRolapLevel() {
194 return rolapLevel;
195 }
196
197 public boolean equals(RolapCubeLevel level) {
198 // verify the levels are part of the same hierarchy
199 return super.equals(level)
200 && getCube().equals(level.getCube());
201 }
202
203 boolean hasClosedPeer() {
204 return rolapLevel.hasClosedPeer();
205 }
206
207 public MemberFormatter getMemberFormatter() {
208 return rolapLevel.getMemberFormatter();
209 }
210
211
212
213 /**
214 * Encapsulation of the difference between levels in terms of how
215 * constraints are generated. There are implementations for 'all' levels,
216 * the 'null' level, parent-child levels and regular levels.
217 */
218 interface LevelReader {
219
220 /**
221 * Adds constraints to a cell request for a member of this level.
222 *
223 * @param member Member to be constrained
224 * @param baseCube base cube if virtual level
225 * @param request Request to be constrained
226 *
227 * @return true if request is unsatisfiable (e.g. if the member is the
228 * null member)
229 */
230 boolean constrainRequest(
231 RolapCubeMember member,
232 RolapCube baseCube,
233 CellRequest request);
234
235 /**
236 * Adds constraints to a cache region for a member of this level.
237 *
238 * @param predicate Predicate
239 * @param baseCube base cube if virtual level
240 * @param cacheRegion Cache region to be constrained
241 */
242 void constrainRegion(
243 StarColumnPredicate predicate,
244 RolapCube baseCube,
245 RolapCacheRegion cacheRegion);
246 }
247
248 /**
249 * Level reader for a regular level.
250 */
251 class RegularLevelReader implements LevelReader {
252 public boolean constrainRequest(
253 RolapCubeMember member,
254 RolapCube baseCube,
255 CellRequest request)
256 {
257 assert member.getLevel() == RolapCubeLevel.this;
258 if (member.getKey() == null) {
259 if (member == member.getHierarchy().getNullMember()) {
260 // cannot form a request if one of the members is null
261 return true;
262 } else {
263 throw Util.newInternal("why is key null?");
264 }
265 }
266
267 RolapStar.Column column = getBaseStarKeyColumn(baseCube);
268
269 if (column == null) {
270 // This hierarchy is not one which qualifies the starMeasure
271 // (this happens in virtual cubes). The starMeasure only has
272 // a value for the 'all' member of the hierarchy (or for the
273 // default member if the hierarchy has no 'all' member)
274 return member != hierarchy.getDefaultMember() ||
275 hierarchy.hasAll();
276 }
277
278 final StarColumnPredicate predicate;
279 if (member.isCalculated()) {
280 predicate = null;
281 } else {
282 predicate = false ? new MemberColumnPredicate(column, member) :
283 new ValueColumnPredicate(column, member.getKey());
284 }
285
286 // use the member as constraint; this will give us some
287 // optimization potential
288 request.addConstrainedColumn(column, predicate);
289
290 if (request.extendedContext &&
291 getNameExp() != null)
292 {
293 final RolapStar.Column nameColumn = column.getNameColumn();
294
295 assert nameColumn != null;
296 request.addConstrainedColumn(nameColumn, null);
297 }
298
299 if (member.isCalculated()) {
300 return false;
301 }
302
303 // If member is unique without reference to its parent,
304 // no further constraint is required.
305 if (isUnique()) {
306 return false;
307 }
308
309 // Constrain the parent member, if any.
310 RolapCubeMember parent = member.getParentMember();
311 while (true) {
312 if (parent == null) {
313 return false;
314 }
315 RolapCubeLevel level = parent.getLevel();
316 final LevelReader levelReader = level.levelReader;
317 if (levelReader == this) {
318 // We are looking at a parent in a parent-child hierarchy,
319 // for example, we have moved from Fred to Fred's boss,
320 // Wilma. We don't want to include Wilma's key in the
321 // request.
322 parent = parent.getParentMember();
323 continue;
324 }
325 return levelReader.constrainRequest(
326 parent, baseCube, request);
327 }
328 }
329
330 public void constrainRegion(
331 StarColumnPredicate predicate,
332 RolapCube baseCube,
333 RolapCacheRegion cacheRegion)
334 {
335 RolapStar.Column column = getBaseStarKeyColumn(baseCube);
336
337 if (column == null) {
338 // This hierarchy is not one which qualifies the starMeasure
339 // (this happens in virtual cubes). The starMeasure only has
340 // a value for the 'all' member of the hierarchy (or for the
341 // default member if the hierarchy has no 'all' member)
342 return;
343 }
344
345 if (predicate instanceof MemberColumnPredicate) {
346 MemberColumnPredicate memberColumnPredicate =
347 (MemberColumnPredicate) predicate;
348 RolapMember member = memberColumnPredicate.getMember();
349 assert member.getLevel() == RolapCubeLevel.this;
350 assert !member.isCalculated();
351 assert memberColumnPredicate.getMember().getKey() != null;
352 assert !member.isNull();
353
354 // use the member as constraint, this will give us some
355 // optimization potential
356 cacheRegion.addPredicate(column, predicate);
357 return;
358 } else if (predicate instanceof RangeColumnPredicate) {
359 RangeColumnPredicate rangeColumnPredicate =
360 (RangeColumnPredicate) predicate;
361 final ValueColumnPredicate lowerBound =
362 rangeColumnPredicate.getLowerBound();
363 RolapMember lowerMember;
364 if (lowerBound == null) {
365 lowerMember = null;
366 } else if (lowerBound instanceof MemberColumnPredicate) {
367 MemberColumnPredicate memberColumnPredicate =
368 (MemberColumnPredicate) lowerBound;
369 lowerMember = memberColumnPredicate.getMember();
370 } else {
371 throw new UnsupportedOperationException();
372 }
373 final ValueColumnPredicate upperBound =
374 rangeColumnPredicate.getUpperBound();
375 RolapMember upperMember;
376 if (upperBound == null) {
377 upperMember = null;
378 } else if (upperBound instanceof MemberColumnPredicate) {
379 MemberColumnPredicate memberColumnPredicate =
380 (MemberColumnPredicate) upperBound;
381 upperMember = memberColumnPredicate.getMember();
382 } else {
383 throw new UnsupportedOperationException();
384 }
385 MemberTuplePredicate predicate2 =
386 new MemberTuplePredicate(
387 baseCube,
388 lowerMember,
389 !rangeColumnPredicate.getLowerInclusive(),
390 upperMember,
391 !rangeColumnPredicate.getUpperInclusive());
392 // use the member as constraint, this will give us some
393 // optimization potential
394 cacheRegion.addPredicate(predicate2);
395 return;
396 }
397
398 // Unknown type of constraint.
399 throw new UnsupportedOperationException();
400 }
401 }
402
403 /**
404 * Level reader for a parent-child level which has a closed peer level.
405 */
406 class ParentChildLevelReaderImpl extends RegularLevelReader {
407 /**
408 * For a parent-child hierarchy with a closure provided by the schema,
409 * the equivalent level in the closed hierarchy; otherwise null.
410 */
411 protected final RolapCubeLevel closedPeer;
412
413 ParentChildLevelReaderImpl(RolapCubeLevel closedPeer) {
414 this.closedPeer = closedPeer;
415 }
416
417 public boolean constrainRequest(
418 RolapCubeMember member,
419 RolapCube baseCube,
420 CellRequest request)
421 {
422
423 // Replace a parent/child level by its closed equivalent, when
424 // available; this is always valid, and improves performance by
425 // enabling the database to compute aggregates.
426 if (member.getDataMember() == null) {
427 // Member has no data member because it IS the data
428 // member of a parent-child hierarchy member. Leave
429 // it be. We don't want to aggregate.
430 return super.constrainRequest(member, baseCube, request);
431 } else if (request.drillThrough) {
432 member = (RolapCubeMember) member.getDataMember();
433 return super.constrainRequest(member, baseCube, request);
434 } else {
435 RolapCubeLevel level = closedPeer;
436 final RolapMember wrappedAllMember =
437 (RolapMember)rolapLevel.getClosedPeer().getHierarchy()
438 .getDefaultMember();
439
440
441 final RolapCubeMember allMember = (RolapCubeMember)
442 level.getHierarchy().getDefaultMember();
443 assert allMember.isAll();
444
445 // isn't creating a member on the fly a bad idea?
446 RolapMember wrappedMember =
447 new RolapMember(
448 wrappedAllMember,
449 rolapLevel.getClosedPeer(),
450 member.getKey());
451 member =
452 new RolapCubeMember(
453 allMember,
454 wrappedMember,
455 closedPeer,
456 RolapCubeLevel.this.getCube());
457
458 return level.getLevelReader().constrainRequest(
459 member, baseCube, request);
460 }
461 }
462
463 public void constrainRegion(
464 StarColumnPredicate predicate,
465 RolapCube baseCube,
466 RolapCacheRegion cacheRegion)
467 {
468 throw new UnsupportedOperationException();
469 }
470 }
471
472 /**
473 * Level reader for the level which contains the 'all' member.
474 */
475 static class AllLevelReaderImpl implements LevelReader {
476 public boolean constrainRequest(
477 RolapCubeMember member,
478 RolapCube baseCube,
479 CellRequest request)
480 {
481 // We don't need to apply any constraints.
482 return false;
483 }
484
485 public void constrainRegion(
486 StarColumnPredicate predicate,
487 RolapCube baseCube,
488 RolapCacheRegion cacheRegion)
489 {
490 // We don't need to apply any constraints.
491 }
492 }
493
494 /**
495 * Level reader for the level which contains the null member.
496 */
497 static class NullLevelReader implements LevelReader {
498 public boolean constrainRequest(
499 RolapCubeMember member,
500 RolapCube baseCube,
501 CellRequest request)
502 {
503 return true;
504 }
505
506 public void constrainRegion(
507 StarColumnPredicate predicate,
508 RolapCube baseCube,
509 RolapCacheRegion cacheRegion)
510 {
511 }
512 }
513
514 }
515
516 // End RolapCubeLevel.java