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