001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2001-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.olap.*; 014import mondrian.resource.MondrianResource; 015import mondrian.spi.Dialect; 016import mondrian.spi.PropertyFormatter; 017import mondrian.spi.impl.Scripts; 018 019import org.apache.log4j.Logger; 020 021import org.olap4j.impl.UnmodifiableArrayMap; 022 023import java.util.*; 024 025/** 026 * <code>RolapLevel</code> implements {@link Level} for a ROLAP database. 027 * 028 * @author jhyde 029 * @since 10 August, 2001 030 */ 031public class RolapLevel extends LevelBase { 032 033 private static final Logger LOGGER = Logger.getLogger(RolapLevel.class); 034 035 /** 036 * The column or expression which yields the level's key. 037 */ 038 protected MondrianDef.Expression keyExp; 039 040 /** 041 * The column or expression which yields the level's ordinal. 042 */ 043 protected MondrianDef.Expression ordinalExp; 044 045 /** 046 * The column or expression which yields the level members' caption. 047 */ 048 protected MondrianDef.Expression captionExp; 049 050 private final Dialect.Datatype datatype; 051 052 private final int flags; 053 054 static final int FLAG_ALL = 0x02; 055 056 /** 057 * For SQL generator. Whether values of "column" are unique globally 058 * unique (as opposed to unique only within the context of the parent 059 * member). 060 */ 061 static final int FLAG_UNIQUE = 0x04; 062 063 private RolapLevel closedPeerLevel; 064 065 protected RolapProperty[] properties; 066 private final RolapProperty[] inheritedProperties; 067 068 /** 069 * Ths expression which gives the name of members of this level. If null, 070 * members are named using the key expression. 071 */ 072 protected MondrianDef.Expression nameExp; 073 /** The expression which joins to the parent member in a parent-child 074 * hierarchy, or null if this is a regular hierarchy. */ 075 protected MondrianDef.Expression parentExp; 076 /** Value which indicates a null parent in a parent-child hierarchy. */ 077 private final String nullParentValue; 078 079 /** Condition under which members are hidden. */ 080 private final HideMemberCondition hideMemberCondition; 081 protected final MondrianDef.Closure xmlClosure; 082 private final Map<String, Annotation> annotationMap; 083 private final SqlStatement.Type internalType; // may be null 084 085 /** 086 * Creates a level. 087 * 088 * @pre parentExp != null || nullParentValue == null 089 * @pre properties != null 090 * @pre levelType != null 091 * @pre hideMemberCondition != null 092 */ 093 RolapLevel( 094 RolapHierarchy hierarchy, 095 String name, 096 String caption, 097 boolean visible, 098 String description, 099 int depth, 100 MondrianDef.Expression keyExp, 101 MondrianDef.Expression nameExp, 102 MondrianDef.Expression captionExp, 103 MondrianDef.Expression ordinalExp, 104 MondrianDef.Expression parentExp, 105 String nullParentValue, 106 MondrianDef.Closure xmlClosure, 107 RolapProperty[] properties, 108 int flags, 109 Dialect.Datatype datatype, 110 SqlStatement.Type internalType, 111 HideMemberCondition hideMemberCondition, 112 LevelType levelType, 113 String approxRowCount, 114 Map<String, Annotation> annotationMap) 115 { 116 super( 117 hierarchy, name, caption, visible, description, depth, levelType); 118 assert annotationMap != null; 119 Util.assertPrecondition(properties != null, "properties != null"); 120 Util.assertPrecondition( 121 hideMemberCondition != null, 122 "hideMemberCondition != null"); 123 Util.assertPrecondition(levelType != null, "levelType != null"); 124 125 if (keyExp instanceof MondrianDef.Column) { 126 checkColumn((MondrianDef.Column) keyExp); 127 } 128 this.annotationMap = annotationMap; 129 this.approxRowCount = loadApproxRowCount(approxRowCount); 130 this.flags = flags; 131 this.datatype = datatype; 132 this.keyExp = keyExp; 133 if (nameExp != null) { 134 if (nameExp instanceof MondrianDef.Column) { 135 checkColumn((MondrianDef.Column) nameExp); 136 } 137 } 138 this.nameExp = nameExp; 139 if (captionExp != null) { 140 if (captionExp instanceof MondrianDef.Column) { 141 checkColumn((MondrianDef.Column) captionExp); 142 } 143 } 144 this.captionExp = captionExp; 145 if (ordinalExp != null) { 146 if (ordinalExp instanceof MondrianDef.Column) { 147 checkColumn((MondrianDef.Column) ordinalExp); 148 } 149 this.ordinalExp = ordinalExp; 150 } else { 151 this.ordinalExp = this.keyExp; 152 } 153 if (parentExp instanceof MondrianDef.Column) { 154 checkColumn((MondrianDef.Column) parentExp); 155 } 156 this.parentExp = parentExp; 157 if (parentExp != null) { 158 Util.assertTrue( 159 !isAll(), 160 "'All' level '" + this + "' must not be parent-child"); 161 Util.assertTrue( 162 isUnique(), 163 "Parent-child level '" + this 164 + "' must have uniqueMembers=\"true\""); 165 } 166 this.nullParentValue = nullParentValue; 167 Util.assertPrecondition( 168 parentExp != null || nullParentValue == null, 169 "parentExp != null || nullParentValue == null"); 170 this.xmlClosure = xmlClosure; 171 for (RolapProperty property : properties) { 172 if (property.getExp() instanceof MondrianDef.Column) { 173 checkColumn((MondrianDef.Column) property.getExp()); 174 } 175 } 176 this.properties = properties; 177 List<Property> list = new ArrayList<Property>(); 178 for (Level level = this; level != null; 179 level = level.getParentLevel()) 180 { 181 final Property[] levelProperties = level.getProperties(); 182 for (final Property levelProperty : levelProperties) { 183 Property existingProperty = lookupProperty( 184 list, levelProperty.getName()); 185 if (existingProperty == null) { 186 list.add(levelProperty); 187 } else if (existingProperty.getType() 188 != levelProperty.getType()) 189 { 190 throw Util.newError( 191 "Property " + this.getName() + "." 192 + levelProperty.getName() + " overrides a " 193 + "property with the same name but different type"); 194 } 195 } 196 } 197 this.inheritedProperties = list.toArray(new RolapProperty[list.size()]); 198 199 Dimension dim = hierarchy.getDimension(); 200 if (dim.getDimensionType() == DimensionType.TimeDimension) { 201 if (!levelType.isTime() && !isAll()) { 202 throw MondrianResource.instance() 203 .NonTimeLevelInTimeHierarchy.ex(getUniqueName()); 204 } 205 } else if (dim.getDimensionType() == null) { 206 // there was no dimension type assigned to the dimension 207 // - check later 208 } else { 209 if (levelType.isTime()) { 210 throw MondrianResource.instance() 211 .TimeLevelInNonTimeHierarchy.ex(getUniqueName()); 212 } 213 } 214 this.internalType = internalType; 215 this.hideMemberCondition = hideMemberCondition; 216 } 217 218 public RolapHierarchy getHierarchy() { 219 return (RolapHierarchy) hierarchy; 220 } 221 222 public Map<String, Annotation> getAnnotationMap() { 223 return annotationMap; 224 } 225 226 private int loadApproxRowCount(String approxRowCount) { 227 boolean notNullAndNumeric = 228 approxRowCount != null 229 && approxRowCount.matches("^\\d+$"); 230 if (notNullAndNumeric) { 231 return Integer.parseInt(approxRowCount); 232 } else { 233 // if approxRowCount is not set, return MIN_VALUE to indicate 234 return Integer.MIN_VALUE; 235 } 236 } 237 238 protected Logger getLogger() { 239 return LOGGER; 240 } 241 242 String getTableName() { 243 String tableName = null; 244 245 MondrianDef.Expression expr = getKeyExp(); 246 if (expr instanceof MondrianDef.Column) { 247 MondrianDef.Column mc = (MondrianDef.Column) expr; 248 tableName = mc.getTableAlias(); 249 } 250 return tableName; 251 } 252 253 public MondrianDef.Expression getKeyExp() { 254 return keyExp; 255 } 256 257 MondrianDef.Expression getOrdinalExp() { 258 return ordinalExp; 259 } 260 261 public MondrianDef.Expression getCaptionExp() { 262 return captionExp; 263 } 264 265 public boolean hasCaptionColumn() { 266 return captionExp != null; 267 } 268 269 final int getFlags() { 270 return flags; 271 } 272 273 HideMemberCondition getHideMemberCondition() { 274 return hideMemberCondition; 275 } 276 277 public final boolean isUnique() { 278 return (flags & FLAG_UNIQUE) != 0; 279 } 280 281 final Dialect.Datatype getDatatype() { 282 return datatype; 283 } 284 285 final String getNullParentValue() { 286 return nullParentValue; 287 } 288 289 /** 290 * Returns whether this level is parent-child. 291 */ 292 public boolean isParentChild() { 293 return parentExp != null; 294 } 295 296 MondrianDef.Expression getParentExp() { 297 return parentExp; 298 } 299 300 // RME: this has to be public for two of the DrillThroughTest test. 301 public 302 MondrianDef.Expression getNameExp() { 303 return nameExp; 304 } 305 306 private Property lookupProperty(List<Property> list, String propertyName) { 307 for (Property property : list) { 308 if (property.getName().equals(propertyName)) { 309 return property; 310 } 311 } 312 return null; 313 } 314 315 RolapLevel( 316 RolapHierarchy hierarchy, 317 int depth, 318 MondrianDef.Level xmlLevel) 319 { 320 this( 321 hierarchy, 322 xmlLevel.name, 323 xmlLevel.caption, 324 xmlLevel.visible, 325 xmlLevel.description, 326 depth, 327 xmlLevel.getKeyExp(), 328 xmlLevel.getNameExp(), 329 xmlLevel.getCaptionExp(), 330 xmlLevel.getOrdinalExp(), 331 xmlLevel.getParentExp(), 332 xmlLevel.nullParentValue, 333 xmlLevel.closure, 334 createProperties(xmlLevel), 335 (xmlLevel.uniqueMembers ? FLAG_UNIQUE : 0), 336 xmlLevel.getDatatype(), 337 toInternalType(xmlLevel.internalType), 338 HideMemberCondition.valueOf(xmlLevel.hideMemberIf), 339 LevelType.valueOf( 340 xmlLevel.levelType.equals("TimeHalfYear") 341 ? "TimeHalfYears" 342 : xmlLevel.levelType), 343 xmlLevel.approxRowCount, 344 RolapHierarchy.createAnnotationMap(xmlLevel.annotations)); 345 346 if (!Util.isEmpty(xmlLevel.caption)) { 347 setCaption(xmlLevel.caption); 348 } 349 350 final String memberFormatterClassName; 351 final Scripts.ScriptDefinition scriptDefinition; 352 if (xmlLevel.memberFormatter != null) { 353 memberFormatterClassName = xmlLevel.memberFormatter.className; 354 scriptDefinition = 355 RolapSchema.toScriptDef(xmlLevel.memberFormatter.script); 356 } else { 357 memberFormatterClassName = xmlLevel.formatter; 358 scriptDefinition = null; 359 } 360 if (memberFormatterClassName != null || scriptDefinition != null) { 361 try { 362 memberFormatter = 363 RolapSchema.getMemberFormatter( 364 memberFormatterClassName, 365 scriptDefinition); 366 } catch (Exception e) { 367 throw MondrianResource.instance().MemberFormatterLoadFailed.ex( 368 xmlLevel.formatter, getUniqueName(), e); 369 } 370 } 371 } 372 373 // helper for constructor 374 private static RolapProperty[] createProperties( 375 MondrianDef.Level xmlLevel) 376 { 377 List<RolapProperty> list = new ArrayList<RolapProperty>(); 378 final MondrianDef.Expression nameExp = xmlLevel.getNameExp(); 379 380 if (nameExp != null) { 381 list.add( 382 new RolapProperty( 383 Property.NAME.name, Property.Datatype.TYPE_STRING, 384 nameExp, null, null, null, true, 385 Property.NAME.description)); 386 } 387 for (int i = 0; i < xmlLevel.properties.length; i++) { 388 MondrianDef.Property xmlProperty = xmlLevel.properties[i]; 389 390 final PropertyFormatter formatter; 391 final String propertyFormatterClassName; 392 final Scripts.ScriptDefinition scriptDefinition; 393 if (xmlProperty.propertyFormatter != null) { 394 propertyFormatterClassName = 395 xmlProperty.propertyFormatter.className; 396 scriptDefinition = 397 RolapSchema.toScriptDef( 398 xmlProperty.propertyFormatter.script); 399 } else { 400 propertyFormatterClassName = xmlProperty.formatter; 401 scriptDefinition = null; 402 } 403 if (propertyFormatterClassName != null 404 || scriptDefinition != null) 405 { 406 try { 407 formatter = 408 RolapSchema.createPropertyFormatter( 409 propertyFormatterClassName, 410 scriptDefinition); 411 } catch (Exception e) { 412 throw MondrianResource.instance() 413 .PropertyFormatterLoadFailed.ex( 414 propertyFormatterClassName, xmlProperty.name, e); 415 } 416 } else { 417 formatter = null; 418 } 419 420 list.add( 421 new RolapProperty( 422 xmlProperty.name, 423 convertPropertyTypeNameToCode(xmlProperty.type), 424 xmlLevel.getPropertyExp(i), 425 formatter, 426 xmlProperty.caption, 427 xmlLevel.properties[i].dependsOnLevelValue, 428 false, 429 xmlProperty.description)); 430 } 431 return list.toArray(new RolapProperty[list.size()]); 432 } 433 434 private static Property.Datatype convertPropertyTypeNameToCode( 435 String type) 436 { 437 if (type.equals("String")) { 438 return Property.Datatype.TYPE_STRING; 439 } else if (type.equals("Numeric")) { 440 return Property.Datatype.TYPE_NUMERIC; 441 } else if (type.equals("Integer")) { 442 return Property.Datatype.TYPE_NUMERIC; 443 } else if (type.equals("Boolean")) { 444 return Property.Datatype.TYPE_BOOLEAN; 445 } else if (type.equals("Timestamp")) { 446 return Property.Datatype.TYPE_TIMESTAMP; 447 } else if (type.equals("Time")) { 448 return Property.Datatype.TYPE_TIME; 449 } else if (type.equals("Date")) { 450 return Property.Datatype.TYPE_DATE; 451 } else { 452 throw Util.newError("Unknown property type '" + type + "'"); 453 } 454 } 455 456 private void checkColumn(MondrianDef.Column nameColumn) { 457 final RolapHierarchy rolapHierarchy = (RolapHierarchy) hierarchy; 458 if (nameColumn.table == null) { 459 final MondrianDef.Relation table = rolapHierarchy.getUniqueTable(); 460 if (table == null) { 461 throw Util.newError( 462 "must specify a table for level " + getUniqueName() 463 + " because hierarchy has more than one table"); 464 } 465 nameColumn.table = table.getAlias(); 466 } else { 467 if (!rolapHierarchy.tableExists(nameColumn.table)) { 468 throw Util.newError( 469 "Table '" + nameColumn.table + "' not found"); 470 } 471 } 472 } 473 474 void init(MondrianDef.CubeDimension xmlDimension) { 475 if (xmlClosure != null) { 476 final RolapDimension dimension = ((RolapHierarchy) hierarchy) 477 .createClosedPeerDimension(this, xmlClosure, xmlDimension); 478 closedPeerLevel = 479 (RolapLevel) dimension.getHierarchies()[0].getLevels()[1]; 480 } 481 } 482 483 public final boolean isAll() { 484 return (flags & FLAG_ALL) != 0; 485 } 486 487 public boolean areMembersUnique() { 488 return (depth == 0) || (depth == 1) && hierarchy.hasAll(); 489 } 490 491 public String getTableAlias() { 492 return keyExp.getTableAlias(); 493 } 494 495 public RolapProperty[] getProperties() { 496 return properties; 497 } 498 499 public Property[] getInheritedProperties() { 500 return inheritedProperties; 501 } 502 503 public int getApproxRowCount() { 504 return approxRowCount; 505 } 506 507 private static final Map<String, SqlStatement.Type> VALUES = 508 UnmodifiableArrayMap.of( 509 "int", SqlStatement.Type.INT, 510 "double", SqlStatement.Type.DOUBLE, 511 "Object", SqlStatement.Type.OBJECT, 512 "String", SqlStatement.Type.STRING, 513 "long", SqlStatement.Type.LONG); 514 515 private static SqlStatement.Type toInternalType(String internalTypeName) { 516 SqlStatement.Type type = VALUES.get(internalTypeName); 517 if (type == null && internalTypeName != null) { 518 throw Util.newError( 519 "Invalid value '" + internalTypeName 520 + "' for attribute 'internalType' of element 'Level'. " 521 + "Valid values are: " 522 + VALUES.keySet()); 523 } 524 return type; 525 } 526 527 public SqlStatement.Type getInternalType() { 528 return internalType; 529 } 530 531 /** 532 * Conditions under which a level's members may be hidden (thereby creating 533 * a <dfn>ragged hierarchy</dfn>). 534 */ 535 public enum HideMemberCondition { 536 /** A member always appears. */ 537 Never, 538 539 /** A member doesn't appear if its name is null or empty. */ 540 IfBlankName, 541 542 /** A member appears unless its name matches its parent's. */ 543 IfParentsName 544 } 545 546 public OlapElement lookupChild(SchemaReader schemaReader, Id.Segment name) { 547 return lookupChild(schemaReader, name, MatchType.EXACT); 548 } 549 550 public OlapElement lookupChild( 551 SchemaReader schemaReader, Id.Segment name, MatchType matchType) 552 { 553 if (name instanceof Id.KeySegment) { 554 Id.KeySegment keySegment = (Id.KeySegment) name; 555 List<Comparable> keyValues = new ArrayList<Comparable>(); 556 for (Id.NameSegment nameSegment : keySegment.getKeyParts()) { 557 final String keyValue = nameSegment.name; 558 if (RolapUtil.mdxNullLiteral().equalsIgnoreCase(keyValue)) { 559 keyValues.add(RolapUtil.sqlNullValue); 560 } else { 561 keyValues.add(keyValue); 562 } 563 } 564 final List<MondrianDef.Expression> keyExps = getInheritedKeyExps(); 565 if (keyExps.size() != keyValues.size()) { 566 throw Util.newError( 567 "Wrong number of values in member key; " 568 + keySegment + " has " + keyValues.size() 569 + " values, whereas level's key has " + keyExps.size() 570 + " columns " 571 + new AbstractList<String>() { 572 public String get(int index) { 573 return keyExps.get(index).getGenericExpression(); 574 } 575 576 public int size() { 577 return keyExps.size(); 578 } 579 } 580 + "."); 581 } 582 return getHierarchy().getMemberReader().getMemberByKey( 583 this, keyValues); 584 } 585 List<Member> levelMembers = schemaReader.getLevelMembers(this, true); 586 if (levelMembers.size() > 0) { 587 Member parent = levelMembers.get(0).getParentMember(); 588 return 589 RolapUtil.findBestMemberMatch( 590 levelMembers, 591 (RolapMember) parent, 592 this, 593 name, 594 matchType); 595 } 596 return null; 597 } 598 599 private List<MondrianDef.Expression> getInheritedKeyExps() { 600 final List<MondrianDef.Expression> list = 601 new ArrayList<MondrianDef.Expression>(); 602 for (RolapLevel x = this;; x = (RolapLevel) x.getParentLevel()) { 603 final MondrianDef.Expression keyExp1 = x.getKeyExp(); 604 if (keyExp1 != null) { 605 list.add(keyExp1); 606 } 607 if (x.isUnique()) { 608 break; 609 } 610 } 611 return list; 612 } 613 614 /** 615 * Indicates that level is not ragged and not a parent/child level. 616 */ 617 public boolean isSimple() { 618 // most ragged hierarchies are not simple -- see isTooRagged. 619 if (isTooRagged()) { 620 return false; 621 } 622 if (isParentChild()) { 623 return false; 624 } 625 // does not work for measures 626 if (isMeasure()) { 627 return false; 628 } 629 return true; 630 } 631 632 /** 633 * Determines whether the specified level is too ragged for native 634 * evaluation, which is able to handle one special case of a ragged 635 * hierarchy: when the level specified in the query is the leaf level of 636 * the hierarchy and HideMemberCondition for the level is IfBlankName. 637 * This is true even if higher levels of the hierarchy can be hidden 638 * because even in that case the only column that needs to be read is the 639 * column that holds the leaf. IfParentsName can't be handled even at the 640 * leaf level because in the general case we aren't reading the column 641 * that holds the parent. Also, IfBlankName can't be handled for non-leaf 642 * levels because we would have to read the column for the next level 643 * down for members with blank names. 644 * 645 * @return true if the specified level is too ragged for native 646 * evaluation. 647 */ 648 private boolean isTooRagged() { 649 // Is this the special case of raggedness that native evaluation 650 // is able to handle? 651 if (getDepth() == getHierarchy().getLevels().length - 1) { 652 switch (getHideMemberCondition()) { 653 case Never: 654 case IfBlankName: 655 return false; 656 default: 657 return true; 658 } 659 } 660 // Handle the general case in the traditional way. 661 return getHierarchy().isRagged(); 662 } 663 664 665 /** 666 * Returns true when the level is part of a parent/child hierarchy and has 667 * an equivalent closed level. 668 */ 669 boolean hasClosedPeer() { 670 return closedPeerLevel != null; 671 } 672 673 public RolapLevel getClosedPeer() { 674 return closedPeerLevel; 675 } 676 677 public static RolapLevel lookupLevel( 678 RolapLevel[] levels, 679 String levelName) 680 { 681 for (RolapLevel level : levels) { 682 if (level.getName().equals(levelName)) { 683 return level; 684 } 685 } 686 return null; 687 } 688} 689 690// End RolapLevel.java