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.calc.TupleList; 014import mondrian.olap.*; 015import mondrian.olap.fun.FunUtil; 016import mondrian.resource.MondrianResource; 017import mondrian.rolap.agg.AggregationManager; 018import mondrian.rolap.agg.CellRequest; 019import mondrian.rolap.aggmatcher.AggStar; 020import mondrian.rolap.sql.*; 021import mondrian.server.Locus; 022import mondrian.server.monitor.SqlStatementEvent; 023import mondrian.spi.Dialect; 024import mondrian.util.*; 025 026import org.eigenbase.util.property.StringProperty; 027 028import java.sql.*; 029import java.util.*; 030 031import javax.sql.DataSource; 032 033/** 034 * A <code>SqlMemberSource</code> reads members from a SQL database. 035 * 036 * <p>It's a good idea to put a {@link CacheMemberReader} on top of this. 037 * 038 * @author jhyde 039 * @since 21 December, 2001 040 */ 041class SqlMemberSource 042 implements MemberReader, SqlTupleReader.MemberBuilder 043{ 044 private final SqlConstraintFactory sqlConstraintFactory = 045 SqlConstraintFactory.instance(); 046 private final RolapHierarchy hierarchy; 047 private final DataSource dataSource; 048 private MemberCache cache; 049 private int lastOrdinal = 0; 050 private boolean assignOrderKeys; 051 private Map<Object, Object> valuePool; 052 053 SqlMemberSource(RolapHierarchy hierarchy) { 054 this.hierarchy = hierarchy; 055 this.dataSource = 056 hierarchy.getRolapSchema().getInternalConnection().getDataSource(); 057 assignOrderKeys = 058 MondrianProperties.instance().CompareSiblingsByOrderKey.get(); 059 valuePool = ValuePoolFactoryFactory.getValuePoolFactory().create(this); 060 } 061 062 // implement MemberSource 063 public RolapHierarchy getHierarchy() { 064 return hierarchy; 065 } 066 067 // implement MemberSource 068 public boolean setCache(MemberCache cache) { 069 this.cache = cache; 070 return true; // yes, we support cache writeback 071 } 072 073 // implement MemberSource 074 public int getMemberCount() { 075 RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); 076 int count = 0; 077 for (RolapLevel level : levels) { 078 count += getLevelMemberCount(level); 079 } 080 return count; 081 } 082 083 public RolapMember substitute(RolapMember member) { 084 return member; 085 } 086 087 public RolapMember desubstitute(RolapMember member) { 088 return member; 089 } 090 091 public RolapMember getMemberByKey( 092 RolapLevel level, 093 List<Comparable> keyValues) 094 { 095 if (level.isAll()) { 096 return null; 097 } 098 List<Dialect.Datatype> datatypeList = new ArrayList<Dialect.Datatype>(); 099 List<MondrianDef.Expression> columnList = 100 new ArrayList<MondrianDef.Expression>(); 101 for (RolapLevel x = level;; x = (RolapLevel) x.getParentLevel()) { 102 columnList.add(x.getKeyExp()); 103 datatypeList.add(x.getDatatype()); 104 if (x.isUnique()) { 105 break; 106 } 107 } 108 final List<RolapMember> list = 109 getMembersInLevel( 110 level, 111 new MemberKeyConstraint( 112 columnList, 113 datatypeList, 114 keyValues)); 115 switch (list.size()) { 116 case 0: 117 return null; 118 case 1: 119 return list.get(0); 120 default: 121 throw Util.newError( 122 "More than one member in level " + level + " with key " 123 + keyValues); 124 } 125 } 126 127 public RolapMember lookupMember( 128 List<Id.Segment> uniqueNameParts, 129 boolean failIfNotFound) 130 { 131 throw new UnsupportedOperationException(); 132 } 133 134 public int getLevelMemberCount(RolapLevel level) { 135 if (level.isAll()) { 136 return 1; 137 } 138 return getMemberCount(level, dataSource); 139 } 140 141 private int getMemberCount(RolapLevel level, DataSource dataSource) { 142 boolean[] mustCount = new boolean[1]; 143 String sql = makeLevelMemberCountSql(level, dataSource, mustCount); 144 final SqlStatement stmt = 145 RolapUtil.executeQuery( 146 dataSource, 147 sql, 148 new Locus( 149 Locus.peek().execution, 150 "SqlMemberSource.getLevelMemberCount", 151 "while counting members of level '" + level)); 152 try { 153 ResultSet resultSet = stmt.getResultSet(); 154 int count; 155 if (! mustCount[0]) { 156 Util.assertTrue(resultSet.next()); 157 ++stmt.rowCount; 158 count = resultSet.getInt(1); 159 } else { 160 // count distinct "manually" 161 ResultSetMetaData rmd = resultSet.getMetaData(); 162 int nColumns = rmd.getColumnCount(); 163 String[] colStrings = new String[nColumns]; 164 count = 0; 165 while (resultSet.next()) { 166 ++stmt.rowCount; 167 boolean isEqual = true; 168 for (int i = 0; i < nColumns; i++) { 169 String colStr = resultSet.getString(i + 1); 170 if (!Util.equals(colStr, colStrings[i])) { 171 isEqual = false; 172 } 173 colStrings[i] = colStr; 174 } 175 if (!isEqual) { 176 count++; 177 } 178 } 179 } 180 return count; 181 } catch (SQLException e) { 182 throw stmt.handle(e); 183 } finally { 184 stmt.close(); 185 } 186 } 187 188 /** 189 * Generates the SQL statement to count the members in 190 * <code>level</code>. For example, <blockquote> 191 * 192 * <pre>SELECT count(*) FROM ( 193 * SELECT DISTINCT "country", "state_province" 194 * FROM "customer") AS "init"</pre> 195 * 196 * </blockquote> counts the non-leaf "state_province" level. MySQL 197 * doesn't allow SELECT-in-FROM, so we use the syntax<blockquote> 198 * 199 * <pre>SELECT count(DISTINCT "country", "state_province") 200 * FROM "customer"</pre> 201 * 202 * </blockquote>. The leaf level requires a different query:<blockquote> 203 * 204 * <pre>SELECT count(*) FROM "customer"</pre> 205 * 206 * </blockquote> counts the leaf "name" level of the "customer" hierarchy. 207 */ 208 private String makeLevelMemberCountSql( 209 RolapLevel level, 210 DataSource dataSource, 211 boolean[] mustCount) 212 { 213 mustCount[0] = false; 214 SqlQuery sqlQuery = 215 SqlQuery.newQuery( 216 dataSource, 217 "while generating query to count members in level " + level); 218 int levelDepth = level.getDepth(); 219 RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); 220 if (levelDepth == levels.length) { 221 // "select count(*) from schema.customer" 222 sqlQuery.addSelect("count(*)", null); 223 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 224 return sqlQuery.toString(); 225 } 226 if (!sqlQuery.getDialect().allowsFromQuery()) { 227 List<String> columnList = new ArrayList<String>(); 228 int columnCount = 0; 229 for (int i = levelDepth; i >= 0; i--) { 230 RolapLevel level2 = levels[i]; 231 if (level2.isAll()) { 232 continue; 233 } 234 if (columnCount > 0) { 235 if (sqlQuery.getDialect().allowsCompoundCountDistinct()) { 236 // no op. 237 } else if (true) { 238 // for databases where both SELECT-in-FROM and 239 // COUNT DISTINCT do not work, we do not 240 // generate any count and do the count 241 // distinct "manually". 242 mustCount[0] = true; 243 } 244 } 245 hierarchy.addToFrom(sqlQuery, level2.getKeyExp()); 246 247 String keyExp = level2.getKeyExp().getExpression(sqlQuery); 248 if (columnCount > 0 249 && !sqlQuery.getDialect().allowsCompoundCountDistinct() 250 && sqlQuery.getDialect().getDatabaseProduct() 251 == Dialect.DatabaseProduct.SYBASE) 252 { 253 keyExp = "convert(varchar, " + columnList + ")"; 254 } 255 columnList.add(keyExp); 256 257 if (level2.isUnique()) { 258 break; // no further qualification needed 259 } 260 ++columnCount; 261 } 262 if (mustCount[0]) { 263 for (String colDef : columnList) { 264 final String exp = 265 sqlQuery.getDialect().generateCountExpression(colDef); 266 sqlQuery.addSelect(exp, null); 267 sqlQuery.addOrderBy(exp, true, false, true); 268 } 269 } else { 270 int i = 0; 271 StringBuilder sb = new StringBuilder(); 272 for (String colDef : columnList) { 273 if (i > 0) { 274 sb.append(", "); 275 } 276 sb.append( 277 sqlQuery.getDialect() 278 .generateCountExpression(colDef)); 279 } 280 sqlQuery.addSelect( 281 "count(DISTINCT " + sb.toString() + ")", null); 282 } 283 return sqlQuery.toString(); 284 285 } else { 286 sqlQuery.setDistinct(true); 287 for (int i = levelDepth; i >= 0; i--) { 288 RolapLevel level2 = levels[i]; 289 if (level2.isAll()) { 290 continue; 291 } 292 MondrianDef.Expression keyExp = level2.getKeyExp(); 293 hierarchy.addToFrom(sqlQuery, keyExp); 294 sqlQuery.addSelect(keyExp.getExpression(sqlQuery), null); 295 if (level2.isUnique()) { 296 break; // no further qualification needed 297 } 298 } 299 SqlQuery outerQuery = 300 SqlQuery.newQuery( 301 dataSource, 302 "while generating query to count members in level " 303 + level); 304 outerQuery.addSelect("count(*)", null); 305 // Note: the "init" is for Postgres, which requires 306 // FROM-queries to have an alias 307 boolean failIfExists = true; 308 outerQuery.addFrom(sqlQuery, "init", failIfExists); 309 return outerQuery.toString(); 310 } 311 } 312 313 314 public List<RolapMember> getMembers() { 315 return getMembers(dataSource); 316 } 317 318 private List<RolapMember> getMembers(DataSource dataSource) { 319 Pair<String, List<SqlStatement.Type>> pair = makeKeysSql(dataSource); 320 final String sql = pair.left; 321 List<SqlStatement.Type> types = pair.right; 322 RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); 323 SqlStatement stmt = 324 RolapUtil.executeQuery( 325 dataSource, sql, types, 0, 0, 326 new SqlStatement.StatementLocus( 327 null, 328 "SqlMemberSource.getMembers", 329 "while building member cache", 330 SqlStatementEvent.Purpose.TUPLES, 0), 331 -1, -1, null); 332 try { 333 final List<SqlStatement.Accessor> accessors = stmt.getAccessors(); 334 List<RolapMember> list = new ArrayList<RolapMember>(); 335 Map<MemberKey, RolapMember> map = 336 new HashMap<MemberKey, RolapMember>(); 337 RolapMember root = null; 338 if (hierarchy.hasAll()) { 339 root = hierarchy.getAllMember(); 340 list.add(root); 341 } 342 343 int limit = MondrianProperties.instance().ResultLimit.get(); 344 ResultSet resultSet = stmt.getResultSet(); 345 while (resultSet.next()) { 346 ++stmt.rowCount; 347 if (limit > 0 && limit < stmt.rowCount) { 348 // result limit exceeded, throw an exception 349 throw stmt.handle( 350 MondrianResource.instance().MemberFetchLimitExceeded.ex( 351 limit)); 352 } 353 354 int column = 0; 355 RolapMember member = root; 356 for (RolapLevel level : levels) { 357 if (level.isAll()) { 358 continue; 359 } 360 Object value = accessors.get(column).get(); 361 if (value == null) { 362 value = RolapUtil.sqlNullValue; 363 } 364 RolapMember parent = member; 365 MemberKey key = new MemberKey(parent, value); 366 member = map.get(key); 367 if (member == null) { 368 RolapMemberBase memberBase = 369 new RolapMemberBase(parent, level, value); 370 memberBase.setOrdinal(lastOrdinal++); 371 member = memberBase; 372/* 373RME is this right 374 if (level.getOrdinalExp() != level.getKeyExp()) { 375 member.setOrdinal(lastOrdinal++); 376 } 377*/ 378 if (value == RolapUtil.sqlNullValue) { 379 addAsOldestSibling(list, member); 380 } else { 381 list.add(member); 382 } 383 map.put(key, member); 384 } 385 column++; 386 387 // REVIEW jvs 20-Feb-2007: What about caption? 388 389 if (!level.getOrdinalExp().equals(level.getKeyExp())) { 390 if (assignOrderKeys) { 391 Object orderKey = accessors.get(column).get(); 392 setOrderKey((RolapMemberBase) member, orderKey); 393 } 394 column++; 395 } 396 397 Property[] properties = level.getProperties(); 398 for (Property property : properties) { 399 // REVIEW emcdermid 9-Jul-2009: 400 // Should we also look up the value in the 401 // pool here, rather than setting it directly? 402 // Presumably the value is already in the pool 403 // as a result of makeMember(). 404 member.setProperty( 405 property.getName(), 406 accessors.get(column).get()); 407 column++; 408 } 409 } 410 } 411 412 return list; 413 } catch (SQLException e) { 414 throw stmt.handle(e); 415 } finally { 416 stmt.close(); 417 } 418 } 419 420 private void setOrderKey(RolapMemberBase member, Object orderKey) { 421 if ((orderKey != null) && !(orderKey instanceof Comparable)) { 422 orderKey = orderKey.toString(); 423 } 424 member.setOrderKey((Comparable<?>) orderKey); 425 } 426 427 /** 428 * Adds <code>member</code> just before the first element in 429 * <code>list</code> which has the same parent. 430 */ 431 private void addAsOldestSibling( 432 List<RolapMember> list, 433 RolapMember member) 434 { 435 int i = list.size(); 436 while (--i >= 0) { 437 RolapMember sibling = list.get(i); 438 if (sibling.getParentMember() != member.getParentMember()) { 439 break; 440 } 441 } 442 list.add(i + 1, member); 443 } 444 445 private Pair<String, List<SqlStatement.Type>> makeKeysSql( 446 DataSource dataSource) 447 { 448 SqlQuery sqlQuery = 449 SqlQuery.newQuery( 450 dataSource, 451 "while generating query to retrieve members of " + hierarchy); 452 RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); 453 for (RolapLevel level : levels) { 454 if (level.isAll()) { 455 continue; 456 } 457 MondrianDef.Expression exp = level.getKeyExp(); 458 hierarchy.addToFrom(sqlQuery, exp); 459 String expString = exp.getExpression(sqlQuery); 460 sqlQuery.addSelectGroupBy(expString, null); 461 exp = level.getOrdinalExp(); 462 hierarchy.addToFrom(sqlQuery, exp); 463 expString = exp.getExpression(sqlQuery); 464 sqlQuery.addOrderBy(expString, true, false, true); 465 if (!exp.equals(level.getKeyExp())) { 466 sqlQuery.addSelect(expString, null); 467 } 468 469 RolapProperty[] properties = level.getProperties(); 470 for (RolapProperty property : properties) { 471 exp = property.getExp(); 472 hierarchy.addToFrom(sqlQuery, exp); 473 expString = exp.getExpression(sqlQuery); 474 String alias = sqlQuery.addSelect(expString, null); 475 // Some dialects allow us to eliminate properties from the 476 // group by that are functionally dependent on the level value 477 if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() 478 || !property.dependsOnLevelValue()) 479 { 480 sqlQuery.addGroupBy(expString, alias); 481 } 482 } 483 } 484 return sqlQuery.toSqlAndTypes(); 485 } 486 487 // implement MemberReader 488 public List<RolapMember> getMembersInLevel( 489 RolapLevel level) 490 { 491 TupleConstraint constraint = 492 sqlConstraintFactory.getLevelMembersConstraint(null); 493 return getMembersInLevel(level, constraint); 494 } 495 496 public List<RolapMember> getMembersInLevel( 497 RolapLevel level, 498 TupleConstraint constraint) 499 { 500 if (level.isAll()) { 501 return Collections.singletonList(hierarchy.getAllMember()); 502 } 503 final TupleReader tupleReader = 504 level.getDimension().isHighCardinality() 505 ? new HighCardSqlTupleReader(constraint) 506 : new SqlTupleReader(constraint); 507 tupleReader.addLevelMembers(level, this, null); 508 final TupleList tupleList = 509 tupleReader.readMembers(dataSource, null, null); 510 511 assert tupleList.getArity() == 1; 512 return Util.cast(tupleList.slice(0)); 513 } 514 515 public MemberCache getMemberCache() { 516 return cache; 517 } 518 519 public Object getMemberCacheLock() { 520 return cache; 521 } 522 523 // implement MemberSource 524 public List<RolapMember> getRootMembers() { 525 return getMembersInLevel((RolapLevel) hierarchy.getLevels()[0]); 526 } 527 528 /** 529 * Generates the SQL statement to access the children of 530 * <code>member</code>. For example, <blockquote> 531 * 532 * <pre>SELECT "city" 533 * FROM "customer" 534 * WHERE "country" = 'USA' 535 * AND "state_province" = 'BC' 536 * GROUP BY "city"</pre> 537 * </blockquote> retrieves the children of the member 538 * <code>[Canada].[BC]</code>. 539 * <p>Note that this method is never called in the context of 540 * virtual cubes, it is only called on regular cubes. 541 * 542 * <p>See also {@link SqlTupleReader#makeLevelMembersSql}. 543 */ 544 Pair<String, List<SqlStatement.Type>> makeChildMemberSql( 545 RolapMember member, 546 DataSource dataSource, 547 MemberChildrenConstraint constraint) 548 { 549 SqlQuery sqlQuery = 550 SqlQuery.newQuery( 551 dataSource, 552 "while generating query to retrieve children of member " 553 + member); 554 555 // If this is a non-empty constraint, it is more efficient to join to 556 // an aggregate table than to the fact table. See whether a suitable 557 // aggregate table exists. 558 AggStar aggStar = chooseAggStar(constraint, member); 559 560 // Create the condition, which is either the parent member or 561 // the full context (non empty). 562 constraint.addMemberConstraint(sqlQuery, null, aggStar, member); 563 564 RolapLevel level = (RolapLevel) member.getLevel().getChildLevel(); 565 566 boolean levelCollapsed = 567 (aggStar != null) 568 && isLevelCollapsed(aggStar, (RolapCubeLevel)level); 569 570 boolean multipleCols = 571 SqlMemberSource.levelContainsMultipleColumns(level); 572 573 if (levelCollapsed && !multipleCols) { 574 // if this is a single column collapsed level, there is 575 // no need to join it with dimension tables 576 RolapStar.Column starColumn = 577 ((RolapCubeLevel) level).getStarKeyColumn(); 578 int bitPos = starColumn.getBitPosition(); 579 AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); 580 String q = aggColumn.generateExprString(sqlQuery); 581 sqlQuery.addSelectGroupBy(q, starColumn.getInternalType()); 582 sqlQuery.addOrderBy(q, true, false, true); 583 aggColumn.getTable().addToFrom(sqlQuery, false, true); 584 return sqlQuery.toSqlAndTypes(); 585 } 586 587 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 588 String q = level.getKeyExp().getExpression(sqlQuery); 589 sqlQuery.addSelectGroupBy(q, level.getInternalType()); 590 591 // in non empty mode the level table must be joined to the fact 592 // table 593 constraint.addLevelConstraint(sqlQuery, null, aggStar, level); 594 595 if (levelCollapsed) { 596 // if this is a collapsed level, add a join between key and aggstar 597 RolapStar.Column starColumn = 598 ((RolapCubeLevel) level).getStarKeyColumn(); 599 int bitPos = starColumn.getBitPosition(); 600 AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); 601 RolapStar.Condition condition = 602 new RolapStar.Condition( 603 level.getKeyExp(), 604 aggColumn.getExpression()); 605 sqlQuery.addWhere(condition.toString(sqlQuery)); 606 hierarchy.addToFromInverse(sqlQuery, level.getKeyExp()); 607 608 // also may need to join parent levels to make selection unique 609 RolapCubeLevel parentLevel = (RolapCubeLevel)level.getParentLevel(); 610 boolean isUnique = level.isUnique(); 611 while (parentLevel != null && !parentLevel.isAll() && !isUnique) { 612 hierarchy.addToFromInverse(sqlQuery, parentLevel.getKeyExp()); 613 starColumn = parentLevel.getStarKeyColumn(); 614 bitPos = starColumn.getBitPosition(); 615 aggColumn = aggStar.lookupColumn(bitPos); 616 condition = 617 new RolapStar.Condition( 618 parentLevel.getKeyExp(), 619 aggColumn.getExpression()); 620 sqlQuery.addWhere(condition.toString(sqlQuery)); 621 parentLevel = parentLevel.getParentLevel(); 622 } 623 } 624 625 if (level.hasCaptionColumn()) { 626 MondrianDef.Expression captionExp = level.getCaptionExp(); 627 if (!levelCollapsed) { 628 hierarchy.addToFrom(sqlQuery, captionExp); 629 } 630 String captionSql = captionExp.getExpression(sqlQuery); 631 sqlQuery.addSelectGroupBy(captionSql, null); 632 } 633 if (!levelCollapsed) { 634 hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); 635 } 636 String orderBy = level.getOrdinalExp().getExpression(sqlQuery); 637 sqlQuery.addOrderBy(orderBy, true, false, true); 638 if (!orderBy.equals(q)) { 639 sqlQuery.addSelectGroupBy(orderBy, null); 640 } 641 642 RolapProperty[] properties = level.getProperties(); 643 for (RolapProperty property : properties) { 644 final MondrianDef.Expression exp = property.getExp(); 645 if (!levelCollapsed) { 646 hierarchy.addToFrom(sqlQuery, exp); 647 } 648 final String s = exp.getExpression(sqlQuery); 649 String alias = sqlQuery.addSelect(s, null); 650 // Some dialects allow us to eliminate properties from the 651 // group by that are functionally dependent on the level value 652 if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() 653 || !property.dependsOnLevelValue()) 654 { 655 sqlQuery.addGroupBy(s, alias); 656 } 657 } 658 return sqlQuery.toSqlAndTypes(); 659 } 660 661 private static AggStar chooseAggStar( 662 MemberChildrenConstraint constraint, 663 RolapMember member) 664 { 665 if (!MondrianProperties.instance().UseAggregates.get() 666 || !(constraint instanceof SqlContextConstraint)) 667 { 668 return null; 669 } 670 SqlContextConstraint contextConstraint = 671 (SqlContextConstraint) constraint; 672 Evaluator evaluator = contextConstraint.getEvaluator(); 673 RolapCube cube = (RolapCube) evaluator.getCube(); 674 RolapStar star = cube.getStar(); 675 final int starColumnCount = star.getColumnCount(); 676 BitKey measureBitKey = BitKey.Factory.makeBitKey(starColumnCount); 677 BitKey levelBitKey = BitKey.Factory.makeBitKey(starColumnCount); 678 679 // Convert global ordinal to cube based ordinal (the 0th dimension 680 // is always [Measures]) 681 final Member[] members = evaluator.getNonAllMembers(); 682 683 // if measure is calculated, we can't continue 684 if (!(members[0] instanceof RolapBaseCubeMeasure)) { 685 return null; 686 } 687 RolapBaseCubeMeasure measure = (RolapBaseCubeMeasure)members[0]; 688 // we need to do more than this! we need the rolap star ordinal, not 689 // the rolap cube 690 691 int bitPosition = 692 ((RolapStar.Measure)measure.getStarMeasure()).getBitPosition(); 693 694 // childLevel will always end up being a RolapCubeLevel, but the API 695 // calls into this method can be both shared RolapMembers and 696 // RolapCubeMembers so this cast is necessary for now. Also note that 697 // this method will never be called in the context of a virtual cube 698 // so baseCube isn't necessary for retrieving the correct column 699 700 // get the level using the current depth 701 RolapCubeLevel childLevel = 702 (RolapCubeLevel) member.getLevel().getChildLevel(); 703 704 RolapStar.Column column = childLevel.getStarKeyColumn(); 705 706 // set a bit for each level which is constrained in the context 707 final CellRequest request = 708 RolapAggregationManager.makeRequest(members); 709 if (request == null) { 710 // One or more calculated members. Cannot use agg table. 711 return null; 712 } 713 // TODO: RME why is this using the array of constrained columns 714 // from the CellRequest rather than just the constrained columns 715 // BitKey (method getConstrainedColumnsBitKey)? 716 RolapStar.Column[] columns = request.getConstrainedColumns(); 717 for (RolapStar.Column column1 : columns) { 718 levelBitKey.set(column1.getBitPosition()); 719 } 720 721 // set the masks 722 levelBitKey.set(column.getBitPosition()); 723 measureBitKey.set(bitPosition); 724 725 // Set the bits for limited rollup members 726 RolapUtil.constraintBitkeyForLimitedMembers( 727 evaluator, members, cube, levelBitKey); 728 729 // find the aggstar using the masks 730 return AggregationManager.findAgg( 731 star, levelBitKey, measureBitKey, new boolean[] {false}); 732 } 733 734 /** 735 * Determine if a level contains more than a single column for its 736 * data, such as an ordinal column or property column 737 * 738 * @param level the level to check 739 * @return true if multiple relational columns are involved in this level 740 */ 741 public static boolean levelContainsMultipleColumns(RolapLevel level) { 742 if (level.isAll()) { 743 return false; 744 } 745 MondrianDef.Expression keyExp = level.getKeyExp(); 746 MondrianDef.Expression ordinalExp = level.getOrdinalExp(); 747 MondrianDef.Expression captionExp = level.getCaptionExp(); 748 749 if (!keyExp.equals(ordinalExp)) { 750 return true; 751 } 752 753 if (captionExp != null && !keyExp.equals(captionExp)) { 754 return true; 755 } 756 757 RolapProperty[] properties = level.getProperties(); 758 for (RolapProperty property : properties) { 759 if (!property.getExp().equals(keyExp)) { 760 return true; 761 } 762 } 763 764 return false; 765 } 766 767 /** 768 * Determine if the given aggregate table has the dimension level 769 * specified within in (AggStar.FactTable) it, aka collapsed, 770 * or associated with foreign keys (AggStar.DimTable) 771 * 772 * @param aggStar aggregate star if exists 773 * @param level level 774 * @return true if agg table has level or not 775 */ 776 public static boolean isLevelCollapsed( 777 AggStar aggStar, 778 RolapCubeLevel level) 779 { 780 boolean levelCollapsed = false; 781 if (level.isAll()) { 782 return levelCollapsed; 783 } 784 RolapStar.Column starColumn = level.getStarKeyColumn(); 785 int bitPos = starColumn.getBitPosition(); 786 AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); 787 if (aggColumn.getTable() instanceof AggStar.FactTable) { 788 levelCollapsed = true; 789 } 790 return levelCollapsed; 791 } 792 793 public void getMemberChildren( 794 List<RolapMember> parentMembers, 795 List<RolapMember> children) 796 { 797 MemberChildrenConstraint constraint = 798 sqlConstraintFactory.getMemberChildrenConstraint(null); 799 getMemberChildren(parentMembers, children, constraint); 800 } 801 802 public Map<? extends Member, Access> getMemberChildren( 803 List<RolapMember> parentMembers, 804 List<RolapMember> children, 805 MemberChildrenConstraint mcc) 806 { 807 // try to fetch all children at once 808 RolapLevel childLevel = 809 getCommonChildLevelForDescendants(parentMembers); 810 if (childLevel != null) { 811 TupleConstraint lmc = 812 sqlConstraintFactory.getDescendantsConstraint( 813 parentMembers, mcc); 814 List<RolapMember> list = 815 getMembersInLevel(childLevel, lmc); 816 children.addAll(list); 817 return Util.toNullValuesMap(children); 818 } 819 820 // fetch them one by one 821 for (RolapMember parentMember : parentMembers) { 822 getMemberChildren(parentMember, children, mcc); 823 } 824 return Util.toNullValuesMap(children); 825 } 826 827 public void getMemberChildren( 828 RolapMember parentMember, 829 List<RolapMember> children) 830 { 831 MemberChildrenConstraint constraint = 832 sqlConstraintFactory.getMemberChildrenConstraint(null); 833 getMemberChildren(parentMember, children, constraint); 834 } 835 836 public Map<? extends Member, Access> getMemberChildren( 837 RolapMember parentMember, 838 List<RolapMember> children, 839 MemberChildrenConstraint constraint) 840 { 841 // allow parent child calculated members through 842 // this fixes the non closure parent child hierarchy bug 843 if (!parentMember.isAll() 844 && parentMember.isCalculated() 845 && !parentMember.getLevel().isParentChild()) 846 { 847 return Util.toNullValuesMap((List)Collections.emptyList()); 848 } 849 getMemberChildren2(parentMember, children, constraint); 850 return Util.toNullValuesMap(children); 851 } 852 853 /** 854 * If all parents belong to the same level and no parent/child is involved, 855 * returns that level; this indicates that all member children can be 856 * fetched at once. Otherwise returns null. 857 */ 858 private RolapLevel getCommonChildLevelForDescendants( 859 List<RolapMember> parents) 860 { 861 // at least two members required 862 if (parents.size() < 2) { 863 return null; 864 } 865 RolapLevel parentLevel = null; 866 RolapLevel childLevel = null; 867 for (RolapMember member : parents) { 868 // we can not fetch children of calc members 869 if (member.isCalculated()) { 870 return null; 871 } 872 // first round? 873 if (parentLevel == null) { 874 parentLevel = member.getLevel(); 875 // check for parent/child 876 if (parentLevel.isParentChild()) { 877 return null; 878 } 879 childLevel = (RolapLevel) parentLevel.getChildLevel(); 880 if (childLevel == null) { 881 return null; 882 } 883 if (childLevel.isParentChild()) { 884 return null; 885 } 886 } else if (parentLevel != member.getLevel()) { 887 return null; 888 } 889 } 890 return childLevel; 891 } 892 893 private void getMemberChildren2( 894 RolapMember parentMember, 895 List<RolapMember> children, 896 MemberChildrenConstraint constraint) 897 { 898 Pair<String, List<SqlStatement.Type>> pair; 899 boolean parentChild; 900 final RolapLevel parentLevel = parentMember.getLevel(); 901 RolapLevel childLevel; 902 if (parentLevel.isParentChild()) { 903 pair = makeChildMemberSqlPC(parentMember); 904 parentChild = true; 905 childLevel = parentLevel; 906 } else { 907 childLevel = (RolapLevel) parentLevel.getChildLevel(); 908 if (childLevel == null) { 909 // member is at last level, so can have no children 910 return; 911 } 912 if (childLevel.isParentChild()) { 913 pair = makeChildMemberSql_PCRoot(parentMember); 914 parentChild = true; 915 } else { 916 pair = makeChildMemberSql(parentMember, dataSource, constraint); 917 parentChild = false; 918 } 919 } 920 final String sql = pair.left; 921 final List<SqlStatement.Type> types = pair.right; 922 SqlStatement stmt = 923 RolapUtil.executeQuery( 924 dataSource, sql, types, 0, 0, 925 new SqlStatement.StatementLocus( 926 Locus.peek().execution, 927 "SqlMemberSource.getMemberChildren", 928 "while building member cache", 929 SqlStatementEvent.Purpose.TUPLES, 0), 930 -1, -1, null); 931 try { 932 int limit = MondrianProperties.instance().ResultLimit.get(); 933 boolean checkCacheStatus = true; 934 935 final List<SqlStatement.Accessor> accessors = stmt.getAccessors(); 936 ResultSet resultSet = stmt.getResultSet(); 937 RolapMember parentMember2 = RolapUtil.strip(parentMember); 938 while (resultSet.next()) { 939 ++stmt.rowCount; 940 if (limit > 0 && limit < stmt.rowCount) { 941 // result limit exceeded, throw an exception 942 throw MondrianResource.instance().MemberFetchLimitExceeded 943 .ex(limit); 944 } 945 946 Object value = accessors.get(0).get(); 947 if (value == null) { 948 value = RolapUtil.sqlNullValue; 949 } 950 Object captionValue; 951 int columnOffset = 1; 952 if (childLevel.hasCaptionColumn()) { 953 // The columnOffset needs to take into account 954 // the caption column if one exists 955 captionValue = accessors.get(columnOffset++).get(); 956 } else { 957 captionValue = null; 958 } 959 Object key = cache.makeKey(parentMember2, value); 960 RolapMember member = cache.getMember(key, checkCacheStatus); 961 checkCacheStatus = false; /* Only check the first time */ 962 if (member == null) { 963 member = 964 makeMember( 965 parentMember2, childLevel, value, captionValue, 966 parentChild, stmt, key, columnOffset); 967 } 968 if (value == RolapUtil.sqlNullValue) { 969 children.toArray(); 970 addAsOldestSibling(children, member); 971 } else { 972 children.add(member); 973 } 974 } 975 } catch (SQLException e) { 976 throw stmt.handle(e); 977 } finally { 978 stmt.close(); 979 } 980 } 981 982 public RolapMember makeMember( 983 RolapMember parentMember, 984 RolapLevel childLevel, 985 Object value, 986 Object captionValue, 987 boolean parentChild, 988 SqlStatement stmt, 989 Object key, 990 int columnOffset) 991 throws SQLException 992 { 993 final RolapLevel rolapChildLevel; 994 if (childLevel instanceof RolapCubeLevel) { 995 rolapChildLevel = ((RolapCubeLevel) childLevel).getRolapLevel(); 996 } else { 997 rolapChildLevel = childLevel; 998 } 999 RolapMemberBase member = 1000 new RolapMemberBase(parentMember, rolapChildLevel, value); 1001 if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) { 1002 member.setOrdinal(lastOrdinal++); 1003 } 1004 if (captionValue != null) { 1005 member.setCaption(captionValue.toString()); 1006 } 1007 if (parentChild) { 1008 // Create a 'public' and a 'data' member. The public member is 1009 // calculated, and its value is the aggregation of the data member 1010 // and all of the children. The children and the data member belong 1011 // to the parent member; the data member does not have any 1012 // children. 1013 member = 1014 childLevel.hasClosedPeer() 1015 ? new RolapParentChildMember( 1016 parentMember, rolapChildLevel, value, member) 1017 : new RolapParentChildMemberNoClosure( 1018 parentMember, rolapChildLevel, value, member); 1019 } 1020 Property[] properties = childLevel.getProperties(); 1021 final List<SqlStatement.Accessor> accessors = stmt.getAccessors(); 1022 if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) { 1023 if (assignOrderKeys) { 1024 Object orderKey = accessors.get(columnOffset).get(); 1025 setOrderKey(member, orderKey); 1026 } 1027 ++columnOffset; 1028 } 1029 for (int j = 0; j < properties.length; j++) { 1030 Property property = properties[j]; 1031 member.setProperty( 1032 property.getName(), 1033 getPooledValue(accessors.get(columnOffset + j).get())); 1034 } 1035 cache.putMember(key, member); 1036 return member; 1037 } 1038 1039 public RolapMember allMember() { 1040 final RolapHierarchy rolapHierarchy = 1041 hierarchy instanceof RolapCubeHierarchy 1042 ? ((RolapCubeHierarchy) hierarchy).getRolapHierarchy() 1043 : hierarchy; 1044 return rolapHierarchy.getAllMember(); 1045 } 1046 1047 /** 1048 * <p>Looks up an object (and if needed, stores it) in a cached value pool. 1049 * This permits us to reuse references to an existing object rather than 1050 * create new references to what are essentially duplicates. The intent 1051 * is to allow the duplicate object to be garbage collected earlier, thus 1052 * keeping overall memory requirements down.</p> 1053 * 1054 * <p>If 1055 * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass} 1056 * is not set, then valuePool will be null and no attempt to cache the 1057 * value will be made. The method will simply return the incoming 1058 * object reference.</p> 1059 * 1060 * @param incoming An object to look up. Must be immutable in usage, 1061 * even if not declared as such. 1062 * @return a reference to a cached object equal to the incoming object, 1063 * or to the incoming object if either no cached object was found, 1064 * or caching is disabled. 1065 */ 1066 private Object getPooledValue(Object incoming) { 1067 if (valuePool == null) { 1068 return incoming; 1069 } else { 1070 Object ret = this.valuePool.get(incoming); 1071 if (ret != null) { 1072 return ret; 1073 } else { 1074 this.valuePool.put(incoming, incoming); 1075 return incoming; 1076 } 1077 } 1078 } 1079 1080 /** 1081 * Generates the SQL to find all root members of a parent-child hierarchy. 1082 * For example, <blockquote> 1083 * 1084 * <pre>SELECT "employee_id" 1085 * FROM "employee" 1086 * WHERE "supervisor_id" IS NULL 1087 * GROUP BY "employee_id"</pre> 1088 * </blockquote> retrieves the root members of the <code>[Employee]</code> 1089 * hierarchy. 1090 * 1091 * <p>Currently, parent-child hierarchies may have only one level (plus the 1092 * 'All' level). 1093 */ 1094 private Pair<String, List<SqlStatement.Type>> makeChildMemberSql_PCRoot( 1095 RolapMember member) 1096 { 1097 SqlQuery sqlQuery = 1098 SqlQuery.newQuery( 1099 dataSource, 1100 "while generating query to retrieve children of parent/child " 1101 + "hierarchy member " + member); 1102 Util.assertTrue( 1103 member.isAll(), 1104 "In the current implementation, parent/child hierarchies must " 1105 + "have only one level (plus the 'All' level)."); 1106 1107 RolapLevel level = (RolapLevel) member.getLevel().getChildLevel(); 1108 1109 Util.assertTrue(!level.isAll(), "all level cannot be parent-child"); 1110 Util.assertTrue( 1111 level.isUnique(), "parent-child level '" 1112 + level + "' must be unique"); 1113 1114 hierarchy.addToFrom(sqlQuery, level.getParentExp()); 1115 String parentId = level.getParentExp().getExpression(sqlQuery); 1116 StringBuilder condition = new StringBuilder(64); 1117 condition.append(parentId); 1118 if (level.getNullParentValue() == null 1119 || level.getNullParentValue().equalsIgnoreCase("NULL")) 1120 { 1121 condition.append(" IS NULL"); 1122 } else { 1123 // Quote the value if it doesn't seem to be a number. 1124 try { 1125 Util.discard(Double.parseDouble(level.getNullParentValue())); 1126 condition.append(" = "); 1127 condition.append(level.getNullParentValue()); 1128 } catch (NumberFormatException e) { 1129 condition.append(" = "); 1130 Util.singleQuoteString(level.getNullParentValue(), condition); 1131 } 1132 } 1133 sqlQuery.addWhere(condition.toString()); 1134 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 1135 String childId = level.getKeyExp().getExpression(sqlQuery); 1136 sqlQuery.addSelectGroupBy(childId, level.getInternalType()); 1137 hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); 1138 String orderBy = level.getOrdinalExp().getExpression(sqlQuery); 1139 sqlQuery.addOrderBy(orderBy, true, false, true); 1140 if (!orderBy.equals(childId)) { 1141 sqlQuery.addSelectGroupBy(orderBy, null); 1142 } 1143 1144 RolapProperty[] properties = level.getProperties(); 1145 for (RolapProperty property : properties) { 1146 final MondrianDef.Expression exp = property.getExp(); 1147 hierarchy.addToFrom(sqlQuery, exp); 1148 final String s = exp.getExpression(sqlQuery); 1149 String alias = sqlQuery.addSelect(s, null); 1150 // Some dialects allow us to eliminate properties from the group by 1151 // that are functionally dependent on the level value 1152 if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() 1153 || !property.dependsOnLevelValue()) 1154 { 1155 sqlQuery.addGroupBy(s, alias); 1156 } 1157 } 1158 return sqlQuery.toSqlAndTypes(); 1159 } 1160 1161 /** 1162 * Generates the SQL statement to access the children of 1163 * <code>member</code> in a parent-child hierarchy. For example, 1164 * <blockquote> 1165 * 1166 * <pre>SELECT "employee_id" 1167 * FROM "employee" 1168 * WHERE "supervisor_id" = 5</pre> 1169 * </blockquote> retrieves the children of the member 1170 * <code>[Employee].[5]</code>. 1171 * 1172 * <p>See also {@link SqlTupleReader#makeLevelMembersSql}. 1173 */ 1174 private Pair<String, List<SqlStatement.Type>> makeChildMemberSqlPC( 1175 RolapMember member) 1176 { 1177 SqlQuery sqlQuery = 1178 SqlQuery.newQuery( 1179 dataSource, 1180 "while generating query to retrieve children of " 1181 + "parent/child hierarchy member " + member); 1182 RolapLevel level = member.getLevel(); 1183 1184 Util.assertTrue(!level.isAll(), "all level cannot be parent-child"); 1185 Util.assertTrue( 1186 level.isUnique(), 1187 "parent-child level '" + level + "' must be " + "unique"); 1188 1189 hierarchy.addToFrom(sqlQuery, level.getParentExp()); 1190 String parentId = level.getParentExp().getExpression(sqlQuery); 1191 1192 StringBuilder buf = new StringBuilder(); 1193 sqlQuery.getDialect().quote(buf, member.getKey(), level.getDatatype()); 1194 sqlQuery.addWhere(parentId, " = ", buf.toString()); 1195 1196 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 1197 String childId = level.getKeyExp().getExpression(sqlQuery); 1198 sqlQuery.addSelectGroupBy(childId, level.getInternalType()); 1199 hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); 1200 String orderBy = level.getOrdinalExp().getExpression(sqlQuery); 1201 sqlQuery.addOrderBy(orderBy, true, false, true); 1202 if (!orderBy.equals(childId)) { 1203 sqlQuery.addSelectGroupBy(orderBy, null); 1204 } 1205 1206 RolapProperty[] properties = level.getProperties(); 1207 for (RolapProperty property : properties) { 1208 final MondrianDef.Expression exp = property.getExp(); 1209 hierarchy.addToFrom(sqlQuery, exp); 1210 final String s = exp.getExpression(sqlQuery); 1211 String alias = sqlQuery.addSelect(s, null); 1212 // Some dialects allow us to eliminate properties from the group by 1213 // that are functionally dependent on the level value 1214 if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() 1215 || !property.dependsOnLevelValue()) 1216 { 1217 sqlQuery.addGroupBy(s, alias); 1218 } 1219 } 1220 return sqlQuery.toSqlAndTypes(); 1221 } 1222 1223 // implement MemberReader 1224 public RolapMember getLeadMember(RolapMember member, int n) { 1225 throw new UnsupportedOperationException(); 1226 } 1227 1228 public void getMemberRange( 1229 RolapLevel level, 1230 RolapMember startMember, 1231 RolapMember endMember, 1232 List<RolapMember> memberList) 1233 { 1234 throw new UnsupportedOperationException(); 1235 } 1236 1237 public int compare( 1238 RolapMember m1, 1239 RolapMember m2, 1240 boolean siblingsAreEqual) 1241 { 1242 throw new UnsupportedOperationException(); 1243 } 1244 1245 1246 public TupleReader.MemberBuilder getMemberBuilder() { 1247 return this; 1248 } 1249 1250 public RolapMember getDefaultMember() { 1251 // we expected the CacheMemberReader to implement this 1252 throw new UnsupportedOperationException(); 1253 } 1254 1255 public RolapMember getMemberParent(RolapMember member) { 1256 throw new UnsupportedOperationException(); 1257 } 1258 1259 // ~ -- Inner classes ------------------------------------------------------ 1260 1261 /** 1262 * Member of a parent-child dimension which has a closure table. 1263 * 1264 * <p>When looking up cells, this member will automatically be converted 1265 * to a corresponding member of the auxiliary dimension which maps onto 1266 * the closure table. 1267 */ 1268 private static class RolapParentChildMember extends RolapMemberBase { 1269 private final RolapMember dataMember; 1270 private int depth = 0; 1271 1272 public RolapParentChildMember( 1273 RolapMember parentMember, 1274 RolapLevel childLevel, 1275 Object value, 1276 RolapMember dataMember) 1277 { 1278 super(parentMember, childLevel, value); 1279 this.dataMember = dataMember; 1280 this.depth = (parentMember != null) 1281 ? parentMember.getDepth() + 1 1282 : 0; 1283 } 1284 1285 public Member getDataMember() { 1286 return dataMember; 1287 } 1288 1289 /** 1290 * @return the members's depth 1291 * @see mondrian.olap.Member#getDepth() 1292 */ 1293 public int getDepth() { 1294 return depth; 1295 } 1296 1297 public int getOrdinal() { 1298 return dataMember.getOrdinal(); 1299 } 1300 } 1301 1302 /** 1303 * Member of a parent-child dimension which has no closure table. 1304 * 1305 * <p>This member is calculated. When you ask for its value, it returns 1306 * an expression which aggregates the values of its child members. 1307 * This calculation is very inefficient, and we can only support 1308 * aggregatable measures ("count distinct" is non-aggregatable). 1309 * Unfortunately it's the best we can do without a closure table. 1310 */ 1311 private static class RolapParentChildMemberNoClosure 1312 extends RolapParentChildMember 1313 { 1314 1315 public RolapParentChildMemberNoClosure( 1316 RolapMember parentMember, 1317 RolapLevel childLevel, Object value, RolapMember dataMember) 1318 { 1319 super(parentMember, childLevel, value, dataMember); 1320 } 1321 1322 protected boolean computeCalculated(final MemberType memberType) { 1323 return true; 1324 } 1325 1326 public Exp getExpression() { 1327 return getHierarchy().getAggregateChildrenExpression(); 1328 } 1329 } 1330 1331 /** 1332 * <p>Interface definition for the pluggable factory used to decide 1333 * which implementation of {@link java.util.Map} to use to pool 1334 * reusable values.</p> 1335 */ 1336 public interface ValuePoolFactory { 1337 /** 1338 * <p>Create a new {@link java.util.Map} to be used to pool values. 1339 * The value pool permits us to reuse references to existing objects 1340 * rather than create new references to what are essentially duplicates 1341 * of the same object. The intent is to allow the duplicate object 1342 * to be garbage collected earlier, thus keeping overall memory 1343 * requirements down.</p> 1344 * 1345 * @param source The {@link SqlMemberSource} in which values are 1346 * being pooled. 1347 * @return a new value pool map 1348 */ 1349 Map<Object, Object> create(SqlMemberSource source); 1350 } 1351 1352 /** 1353 * Default {@link mondrian.rolap.SqlMemberSource.ValuePoolFactory} 1354 * implementation, used if 1355 * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass} 1356 * is not set. 1357 */ 1358 public static final class NullValuePoolFactory 1359 implements ValuePoolFactory 1360 { 1361 /** 1362 * {@inheritDoc} 1363 * <p>This version returns null, meaning that 1364 * by default values will not be pooled.</p> 1365 * 1366 * @param source {@inheritDoc} 1367 * @return {@inheritDoc} 1368 */ 1369 public Map<Object, Object> create(SqlMemberSource source) { 1370 return null; 1371 } 1372 } 1373 1374 /** 1375 * <p>Creates the ValuePoolFactory which is in turn used 1376 * to create property-value maps for member properties.</p> 1377 * 1378 * <p>The name of the ValuePoolFactory is drawn from 1379 * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass} 1380 * in mondrian.properties. If unset, it defaults to 1381 * {@link mondrian.rolap.SqlMemberSource.NullValuePoolFactory}. </p> 1382 */ 1383 public static final class ValuePoolFactoryFactory 1384 extends ObjectFactory.Singleton<ValuePoolFactory> 1385 { 1386 /** 1387 * Single instance of the <code>ValuePoolFactoryFactory</code>. 1388 */ 1389 private static final ValuePoolFactoryFactory factory; 1390 static { 1391 factory = new ValuePoolFactoryFactory(); 1392 } 1393 1394 /** 1395 * Access the <code>ValuePoolFactory</code> instance. 1396 * 1397 * @return the <code>Map</code>. 1398 */ 1399 public static ValuePoolFactory getValuePoolFactory() { 1400 return factory.getObject(); 1401 } 1402 1403 /** 1404 * The constructor for the <code>ValuePoolFactoryFactory</code>. 1405 * This passes the <code>ValuePoolFactory</code> class to the 1406 * <code>ObjectFactory</code> base class. 1407 */ 1408 private ValuePoolFactoryFactory() { 1409 super(ValuePoolFactory.class); 1410 } 1411 1412 protected StringProperty getStringProperty() { 1413 return MondrianProperties.instance() 1414 .SqlMemberSourceValuePoolFactoryClass; 1415 } 1416 1417 protected ValuePoolFactory getDefault( 1418 Class[] parameterTypes, 1419 Object[] parameterValues) 1420 throws CreationException 1421 { 1422 return new NullValuePoolFactory(); 1423 } 1424 } 1425} 1426 1427// End SqlMemberSource.java