001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/SqlMemberSource.java#93 $ 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 // jhyde, 21 December, 2001 012 */ 013 014 package mondrian.rolap; 015 016 import mondrian.olap.*; 017 import mondrian.resource.MondrianResource; 018 import mondrian.rolap.sql.*; 019 import mondrian.rolap.aggmatcher.AggStar; 020 import mondrian.rolap.agg.AggregationManager; 021 import mondrian.rolap.agg.CellRequest; 022 023 import javax.sql.DataSource; 024 import java.sql.*; 025 import java.util.*; 026 027 /** 028 * A <code>SqlMemberSource</code> reads members from a SQL database. 029 * 030 * <p>It's a good idea to put a {@link CacheMemberReader} on top of this. 031 * 032 * @author jhyde 033 * @since 21 December, 2001 034 * @version $Id: //open/mondrian/src/main/mondrian/rolap/SqlMemberSource.java#93 $ 035 */ 036 class SqlMemberSource 037 implements MemberReader, SqlTupleReader.MemberBuilder 038 { 039 private final SqlConstraintFactory sqlConstraintFactory = 040 SqlConstraintFactory.instance(); 041 private final RolapHierarchy hierarchy; 042 private final DataSource dataSource; 043 private MemberCache cache; 044 private int lastOrdinal = 0; 045 private boolean assignOrderKeys; 046 047 SqlMemberSource(RolapHierarchy hierarchy) { 048 this.hierarchy = hierarchy; 049 this.dataSource = 050 hierarchy.getRolapSchema().getInternalConnection().getDataSource(); 051 assignOrderKeys = 052 MondrianProperties.instance().CompareSiblingsByOrderKey.get(); 053 } 054 055 // implement MemberSource 056 public RolapHierarchy getHierarchy() { 057 return hierarchy; 058 } 059 060 // implement MemberSource 061 public boolean setCache(MemberCache cache) { 062 this.cache = cache; 063 return true; // yes, we support cache writeback 064 } 065 066 // implement MemberSource 067 public int getMemberCount() { 068 RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); 069 int count = 0; 070 for (RolapLevel level : levels) { 071 count += getLevelMemberCount(level); 072 } 073 return count; 074 } 075 076 public RolapMember substitute(RolapMember member) { 077 return member; 078 } 079 080 public RolapMember desubstitute(RolapMember member) { 081 return member; 082 } 083 084 public RolapMember lookupMember( 085 List<Id.Segment> uniqueNameParts, 086 boolean failIfNotFound) 087 { 088 throw new UnsupportedOperationException(); 089 } 090 091 public int getLevelMemberCount(RolapLevel level) { 092 if (level.isAll()) { 093 return 1; 094 } 095 return getMemberCount(level, dataSource); 096 } 097 098 private int getMemberCount(RolapLevel level, DataSource dataSource) { 099 boolean[] mustCount = new boolean[1]; 100 String sql = makeLevelMemberCountSql(level, dataSource, mustCount); 101 final SqlStatement stmt = 102 RolapUtil.executeQuery( 103 dataSource, sql, "SqlMemberSource.getLevelMemberCount", 104 "while counting members of level '" + level); 105 try { 106 ResultSet resultSet = stmt.getResultSet(); 107 int count; 108 if (! mustCount[0]) { 109 Util.assertTrue(resultSet.next()); 110 ++stmt.rowCount; 111 count = resultSet.getInt(1); 112 } else { 113 // count distinct "manually" 114 ResultSetMetaData rmd = resultSet.getMetaData(); 115 int nColumns = rmd.getColumnCount(); 116 String[] colStrings = new String[nColumns]; 117 count = 0; 118 while (resultSet.next()) { 119 ++stmt.rowCount; 120 boolean isEqual = true; 121 for (int i = 0; i < nColumns; i++) { 122 String colStr = resultSet.getString(i + 1); 123 if (!colStr.equals(colStrings[i])) { 124 isEqual = false; 125 } 126 colStrings[i] = colStr; 127 } 128 if (!isEqual) { 129 count++; 130 } 131 } 132 } 133 return count; 134 } catch (SQLException e) { 135 throw stmt.handle(e); 136 } finally { 137 stmt.close(); 138 } 139 } 140 141 /** 142 * Generates the SQL statement to count the members in 143 * <code>level</code>. For example, <blockquote> 144 * 145 * <pre>SELECT count(*) FROM ( 146 * SELECT DISTINCT "country", "state_province" 147 * FROM "customer") AS "init"</pre> 148 * 149 * </blockquote> counts the non-leaf "state_province" level. MySQL 150 * doesn't allow SELECT-in-FROM, so we use the syntax<blockquote> 151 * 152 * <pre>SELECT count(DISTINCT "country", "state_province") 153 * FROM "customer"</pre> 154 * 155 * </blockquote>. The leaf level requires a different query:<blockquote> 156 * 157 * <pre>SELECT count(*) FROM "customer"</pre> 158 * 159 * </blockquote> counts the leaf "name" level of the "customer" hierarchy. 160 */ 161 private String makeLevelMemberCountSql( 162 RolapLevel level, 163 DataSource dataSource, 164 boolean[] mustCount) 165 { 166 mustCount[0] = false; 167 SqlQuery sqlQuery = 168 SqlQuery.newQuery( 169 dataSource, 170 "while generating query to count members in level " + level); 171 int levelDepth = level.getDepth(); 172 RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); 173 if (levelDepth == levels.length) { 174 // "select count(*) from schema.customer" 175 sqlQuery.addSelect("count(*)"); 176 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 177 return sqlQuery.toString(); 178 } 179 if (!sqlQuery.getDialect().allowsFromQuery()) { 180 String columnList = ""; 181 int columnCount = 0; 182 for (int i = levelDepth; i >= 0; i--) { 183 RolapLevel level2 = levels[i]; 184 if (level2.isAll()) { 185 continue; 186 } 187 if (columnCount > 0) { 188 if (sqlQuery.getDialect().allowsCompoundCountDistinct()) { 189 columnList += ", "; 190 } else if (true) { 191 // for databases where both SELECT-in-FROM and 192 // COUNT DISTINCT do not work, we do not 193 // generate any count and do the count 194 // distinct "manually". 195 mustCount[0] = true; 196 } else if (sqlQuery.getDialect().isSybase()) { 197 // "select count(distinct convert(varchar, c1) + 198 // convert(varchar, c2)) from table" 199 if (columnCount == 1) { 200 // Conversion to varchar is expensive, so we only 201 // do it when we know we are dealing with a 202 // compound key. 203 columnList = "convert(varchar, " + columnList + ")"; 204 } 205 columnList += " + "; 206 } else { 207 // Apparently this database allows neither 208 // SELECT-in-FROM nor compound COUNT DISTINCT. I don't 209 // know any database where this happens. If you receive 210 // this error, try a workaround similar to the Sybase 211 // workaround above. 212 throw Util.newInternal( 213 "Cannot generate query to count members of level '" + 214 level.getUniqueName() + 215 "': database supports neither SELECT-in-FROM nor compound COUNT DISTINCT"); 216 } 217 } 218 hierarchy.addToFrom(sqlQuery, level2.getKeyExp()); 219 220 String keyExp = level2.getKeyExp().getExpression(sqlQuery); 221 if (columnCount > 0 && 222 !sqlQuery.getDialect().allowsCompoundCountDistinct() && 223 sqlQuery.getDialect().isSybase()) { 224 225 keyExp = "convert(varchar, " + columnList + ")"; 226 } 227 columnList += keyExp; 228 229 if (level2.isUnique()) { 230 break; // no further qualification needed 231 } 232 ++columnCount; 233 } 234 if (mustCount[0]) { 235 sqlQuery.addSelect(columnList); 236 sqlQuery.addOrderBy(columnList, true, false, true); 237 } else { 238 sqlQuery.addSelect("count(DISTINCT " + columnList + ")"); 239 } 240 return sqlQuery.toString(); 241 242 } else { 243 sqlQuery.setDistinct(true); 244 for (int i = levelDepth; i >= 0; i--) { 245 RolapLevel level2 = levels[i]; 246 if (level2.isAll()) { 247 continue; 248 } 249 hierarchy.addToFrom(sqlQuery, level2.getKeyExp()); 250 sqlQuery.addSelect(level2.getKeyExp().getExpression(sqlQuery)); 251 if (level2.isUnique()) { 252 break; // no further qualification needed 253 } 254 } 255 SqlQuery outerQuery = 256 SqlQuery.newQuery( 257 dataSource, 258 "while generating query to count members in level " + level); 259 outerQuery.addSelect("count(*)"); 260 // Note: the "init" is for Postgres, which requires 261 // FROM-queries to have an alias 262 boolean failIfExists = true; 263 outerQuery.addFrom(sqlQuery, "init", failIfExists); 264 return outerQuery.toString(); 265 } 266 } 267 268 269 public List<RolapMember> getMembers() { 270 return getMembers(dataSource); 271 } 272 273 private List<RolapMember> getMembers(DataSource dataSource) { 274 String sql = makeKeysSql(dataSource); 275 RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); 276 SqlStatement stmt = 277 RolapUtil.executeQuery( 278 dataSource, sql, "SqlMemberSource.getMembers", 279 "while building member cache"); 280 try { 281 List<RolapMember> list = new ArrayList<RolapMember>(); 282 Map<MemberKey, RolapMember> map = 283 new HashMap<MemberKey, RolapMember>(); 284 RolapMember root = null; 285 if (hierarchy.hasAll()) { 286 root = hierarchy.getAllMember(); 287 list.add(root); 288 } 289 290 int limit = MondrianProperties.instance().ResultLimit.get(); 291 ResultSet resultSet = stmt.getResultSet(); 292 while (resultSet.next()) { 293 ++stmt.rowCount; 294 if (limit > 0 && limit < stmt.rowCount) { 295 // result limit exceeded, throw an exception 296 throw stmt.handle( 297 MondrianResource.instance().MemberFetchLimitExceeded. 298 ex(limit)); 299 } 300 301 int column = 0; 302 RolapMember member = root; 303 for (RolapLevel level : levels) { 304 if (level.isAll()) { 305 continue; 306 } 307 Object value = resultSet.getObject(column + 1); 308 if (value == null) { 309 value = RolapUtil.sqlNullValue; 310 } 311 RolapMember parent = member; 312 MemberKey key = new MemberKey(parent, value); 313 member = map.get(key); 314 if (member == null) { 315 member = new RolapMember(parent, level, value); 316 member.setOrdinal(lastOrdinal++); 317 /* 318 RME is this right 319 if (level.getOrdinalExp() != level.getKeyExp()) { 320 member.setOrdinal(lastOrdinal++); 321 } 322 */ 323 if (value == RolapUtil.sqlNullValue) { 324 addAsOldestSibling(list, member); 325 } else { 326 list.add(member); 327 } 328 map.put(key, member); 329 } 330 column++; 331 332 // REVIEW jvs 20-Feb-2007: What about caption? 333 334 if (!level.getOrdinalExp().equals(level.getKeyExp())) { 335 if (assignOrderKeys) { 336 Object orderKey = resultSet.getObject(column + 1); 337 setOrderKey(member, orderKey); 338 } 339 column++; 340 } 341 342 Property[] properties = level.getProperties(); 343 for (Property property : properties) { 344 member.setProperty(property.getName(), 345 resultSet.getObject(column + 1)); 346 column++; 347 } 348 } 349 } 350 351 return list; 352 } catch (SQLException e) { 353 throw stmt.handle(e); 354 } finally { 355 stmt.close(); 356 } 357 } 358 359 private void setOrderKey(RolapMember member, Object orderKey) { 360 if ((orderKey != null) && !(orderKey instanceof Comparable)) { 361 orderKey = orderKey.toString(); 362 } 363 member.setOrderKey((Comparable) orderKey); 364 } 365 366 /** 367 * Adds <code>member</code> just before the first element in 368 * <code>list</code> which has the same parent. 369 */ 370 private void addAsOldestSibling( 371 List<RolapMember> list, 372 RolapMember member) 373 { 374 int i = list.size(); 375 while (--i >= 0) { 376 RolapMember sibling = list.get(i); 377 if (sibling.getParentMember() != member.getParentMember()) { 378 break; 379 } 380 } 381 list.add(i + 1, member); 382 } 383 384 private String makeKeysSql(DataSource dataSource) { 385 SqlQuery sqlQuery = 386 SqlQuery.newQuery( 387 dataSource, 388 "while generating query to retrieve members of " + hierarchy); 389 RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); 390 for (RolapLevel level : levels) { 391 if (level.isAll()) { 392 continue; 393 } 394 MondrianDef.Expression exp = level.getKeyExp(); 395 hierarchy.addToFrom(sqlQuery, exp); 396 String expString = exp.getExpression(sqlQuery); 397 sqlQuery.addSelect(expString); 398 sqlQuery.addGroupBy(expString); 399 exp = level.getOrdinalExp(); 400 hierarchy.addToFrom(sqlQuery, exp); 401 expString = exp.getExpression(sqlQuery); 402 sqlQuery.addOrderBy(expString, true, false, true); 403 sqlQuery.addGroupBy(expString); 404 if (!exp.equals(level.getKeyExp())) { 405 sqlQuery.addSelect(expString); 406 } 407 408 RolapProperty[] properties = level.getProperties(); 409 for (RolapProperty property : properties) { 410 exp = property.getExp(); 411 hierarchy.addToFrom(sqlQuery, exp); 412 expString = exp.getExpression(sqlQuery); 413 sqlQuery.addSelect(expString); 414 sqlQuery.addGroupBy(expString); 415 } 416 } 417 return sqlQuery.toString(); 418 } 419 420 // implement MemberReader 421 public List<RolapMember> getMembersInLevel( 422 RolapLevel level, 423 int startOrdinal, 424 int endOrdinal) { 425 TupleConstraint constraint = 426 sqlConstraintFactory.getLevelMembersConstraint(null); 427 return getMembersInLevel(level, startOrdinal, endOrdinal, constraint); 428 } 429 430 public List<RolapMember> getMembersInLevel( 431 RolapLevel level, 432 int startOrdinal, 433 int endOrdinal, 434 TupleConstraint constraint) { 435 if (level.isAll()) { 436 final List<RolapMember> list = new ArrayList<RolapMember>(); 437 list.add(hierarchy.getAllMember()); 438 //return Collections.singletonList(hierarchy.getAllMember()); 439 return list; 440 } 441 return getMembersInLevel(level, constraint); 442 } 443 444 private List<RolapMember> getMembersInLevel( 445 RolapLevel level, 446 TupleConstraint constraint) 447 { 448 final TupleReader tupleReader = 449 level.getDimension().isHighCardinality() 450 ? new HighCardSqlTupleReader(constraint) 451 : new SqlTupleReader(constraint); 452 tupleReader.addLevelMembers(level, this, null); 453 final List<RolapMember[]> tupleList = 454 tupleReader.readTuples(dataSource, null, null); 455 456 return new AbstractList<RolapMember>() { 457 public RolapMember get(final int index) { 458 return tupleList.get(index)[0]; 459 } 460 461 public int size() { 462 return tupleList.size(); 463 } 464 465 public mondrian.rolap.RolapMember[] toArray() { 466 final List<Member> l = new ArrayList<Member>(); 467 for (final RolapMember[] tuple : tupleList) { 468 l.add(tuple[0]); 469 } 470 return l.toArray(new RolapMember[l.size()]); 471 } 472 473 public <T> T[] toArray(T[] pattern) { 474 return (T[]) toArray(); 475 } 476 477 public Iterator<RolapMember> iterator() { 478 final Iterator<RolapMember[]> it = tupleList.iterator(); 479 return new Iterator<RolapMember>() { 480 public boolean hasNext() { 481 return it.hasNext(); 482 } 483 public RolapMember next() { 484 return it.next()[0]; 485 } 486 public void remove() { 487 it.remove(); 488 } 489 }; 490 } 491 }; 492 } 493 494 public MemberCache getMemberCache() { 495 return cache; 496 } 497 498 public Object getMemberCacheLock() { 499 return cache; 500 } 501 502 // implement MemberSource 503 public List<RolapMember> getRootMembers() { 504 return getMembersInLevel( 505 (RolapLevel) hierarchy.getLevels()[0], 506 0, 507 Integer.MAX_VALUE); 508 } 509 510 /** 511 * Generates the SQL statement to access the children of 512 * <code>member</code>. For example, <blockquote> 513 * 514 * <pre>SELECT "city" 515 * FROM "customer" 516 * WHERE "country" = 'USA' 517 * AND "state_province" = 'BC' 518 * GROUP BY "city"</pre> 519 * </blockquote> retrieves the children of the member 520 * <code>[Canada].[BC]</code>. 521 * <p>Note that this method is never called in the context of 522 * virtual cubes, it is only called on regular cubes. 523 * 524 * <p>See also {@link SqlTupleReader#makeLevelMembersSql}. 525 */ 526 String makeChildMemberSql( 527 RolapMember member, 528 DataSource dataSource, 529 MemberChildrenConstraint constraint) 530 { 531 SqlQuery sqlQuery = 532 SqlQuery.newQuery( 533 dataSource, 534 "while generating query to retrieve children of member " 535 + member); 536 537 // If this is a non-empty constraint, it is more efficient to join to 538 // an aggregate table than to the fact table. See whether a suitable 539 // aggregate table exists. 540 AggStar aggStar = chooseAggStar(constraint, member); 541 542 // Create the condition, which is either the parent member or 543 // the full context (non empty). 544 constraint.addMemberConstraint(sqlQuery, null, aggStar, member); 545 546 RolapLevel level = (RolapLevel) member.getLevel().getChildLevel(); 547 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 548 String q = level.getKeyExp().getExpression(sqlQuery); 549 sqlQuery.addSelect(q); 550 sqlQuery.addGroupBy(q); 551 552 // in non empty mode the level table must be joined to the fact table 553 constraint.addLevelConstraint(sqlQuery, null, aggStar, level); 554 555 if (level.hasCaptionColumn()) { 556 MondrianDef.Expression captionExp = level.getCaptionExp(); 557 hierarchy.addToFrom(sqlQuery, captionExp); 558 String captionSql = captionExp.getExpression(sqlQuery); 559 sqlQuery.addSelect(captionSql); 560 sqlQuery.addGroupBy(captionSql); 561 } 562 563 hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); 564 String orderBy = level.getOrdinalExp().getExpression(sqlQuery); 565 sqlQuery.addOrderBy(orderBy, true, false, true); 566 if (!orderBy.equals(q)) { 567 sqlQuery.addGroupBy(orderBy); 568 sqlQuery.addSelect(orderBy); 569 } 570 571 RolapProperty[] properties = level.getProperties(); 572 for (RolapProperty property : properties) { 573 final MondrianDef.Expression exp = property.getExp(); 574 hierarchy.addToFrom(sqlQuery, exp); 575 final String s = exp.getExpression(sqlQuery); 576 sqlQuery.addSelect(s); 577 sqlQuery.addGroupBy(s); 578 } 579 return sqlQuery.toString(); 580 } 581 582 private static AggStar chooseAggStar( 583 MemberChildrenConstraint constraint, 584 RolapMember member) 585 { 586 if (!(constraint instanceof SqlContextConstraint)) { 587 return null; 588 } 589 590 SqlContextConstraint contextConstraint = 591 (SqlContextConstraint) constraint; 592 Evaluator evaluator = contextConstraint.getEvaluator(); 593 RolapCube cube = (RolapCube) evaluator.getCube(); 594 RolapStar star = cube.getStar(); 595 final int starColumnCount = star.getColumnCount(); 596 BitKey measureBitKey = BitKey.Factory.makeBitKey(starColumnCount); 597 BitKey levelBitKey = BitKey.Factory.makeBitKey(starColumnCount); 598 599 // Convert global ordinal to cube based ordinal (the 0th dimension 600 // is always [Measures]) 601 final Member[] members = evaluator.getMembers(); 602 Member measure = members[0]; 603 int ordinal = measure.getOrdinal(); 604 605 // childLevel will always end up being a RolapCubeLevel, but the API 606 // calls into this method can be both shared RolapMembers and 607 // RolapCubeMembers so this cast is necessary for now. Also note that 608 // this method will never be called in the context of a virtual cube 609 // so baseCube isn't necessary for retrieving the correct column 610 611 // get the level using the current depth 612 RolapCubeLevel childLevel = 613 (RolapCubeLevel) member.getLevel().getChildLevel(); 614 615 RolapStar.Column column = childLevel.getStarKeyColumn(); 616 617 // set a bit for each level which is constrained in the context 618 final CellRequest request = 619 RolapAggregationManager.makeRequest(members); 620 if (request == null) { 621 // One or more calculated members. Cannot use agg table. 622 return null; 623 } 624 // TODO: RME why is this using the array of constrained columns 625 // from the CellRequest rather than just the constrained columns 626 // BitKey (method getConstrainedColumnsBitKey)? 627 RolapStar.Column[] columns = request.getConstrainedColumns(); 628 for (RolapStar.Column column1 : columns) { 629 levelBitKey.set(column1.getBitPosition()); 630 } 631 632 // set the masks 633 levelBitKey.set(column.getBitPosition()); 634 measureBitKey.set(ordinal); 635 636 // find the aggstar using the masks 637 return AggregationManager.instance().findAgg( 638 star, levelBitKey, measureBitKey, new boolean[]{ false }); 639 } 640 641 public void getMemberChildren(List<RolapMember> parentMembers, List<RolapMember> children) { 642 MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); 643 getMemberChildren(parentMembers, children, constraint); 644 } 645 646 public void getMemberChildren( 647 List<RolapMember> parentMembers, 648 List<RolapMember> children, 649 MemberChildrenConstraint mcc) 650 { 651 // try to fetch all children at once 652 RolapLevel childLevel = 653 getCommonChildLevelForDescendants(parentMembers); 654 if (childLevel != null) { 655 TupleConstraint lmc = 656 sqlConstraintFactory.getDescendantsConstraint( 657 parentMembers, mcc); 658 List<RolapMember> list = 659 getMembersInLevel(childLevel, 0, Integer.MAX_VALUE, lmc); 660 children.addAll(list); 661 return; 662 } 663 664 // fetch them one by one 665 for (RolapMember parentMember : parentMembers) { 666 getMemberChildren(parentMember, children, mcc); 667 } 668 } 669 670 public void getMemberChildren( 671 RolapMember parentMember, 672 List<RolapMember> children) 673 { 674 MemberChildrenConstraint constraint = 675 sqlConstraintFactory.getMemberChildrenConstraint(null); 676 getMemberChildren(parentMember, children, constraint); 677 } 678 679 public void getMemberChildren( 680 RolapMember parentMember, 681 List<RolapMember> children, 682 MemberChildrenConstraint constraint) 683 { 684 // allow parent child calculated members through 685 // this fixes the non closure parent child hierarchy bug 686 if (!parentMember.isAll() && 687 parentMember.isCalculated() && 688 !parentMember.getLevel().isParentChild()) { 689 return; 690 } 691 getMemberChildren2(parentMember, children, constraint); 692 } 693 694 /** 695 * If all parents belong to the same level and no parent/child is involved, 696 * returns that level; this indicates that all member children can be 697 * fetched at once. Otherwise returns null. 698 */ 699 private RolapLevel getCommonChildLevelForDescendants( 700 List<RolapMember> parents) 701 { 702 // at least two members required 703 if (parents.size() < 2) { 704 return null; 705 } 706 RolapLevel parentLevel = null; 707 RolapLevel childLevel = null; 708 for (RolapMember member : parents) { 709 // we can not fetch children of calc members 710 if (member.isCalculated()) { 711 return null; 712 } 713 // first round? 714 if (parentLevel == null) { 715 parentLevel = member.getLevel(); 716 // check for parent/child 717 if (parentLevel.isParentChild()) { 718 return null; 719 } 720 childLevel = (RolapLevel) parentLevel.getChildLevel(); 721 if (childLevel == null) { 722 return null; 723 } 724 if (childLevel.isParentChild()) { 725 return null; 726 } 727 } else if (parentLevel != member.getLevel()) { 728 return null; 729 } 730 } 731 return childLevel; 732 } 733 734 private void getMemberChildren2( 735 RolapMember parentMember, 736 List<RolapMember> children, 737 MemberChildrenConstraint constraint) 738 { 739 String sql; 740 boolean parentChild; 741 final RolapLevel parentLevel = parentMember.getLevel(); 742 RolapLevel childLevel; 743 if (parentLevel.isParentChild()) { 744 sql = makeChildMemberSqlPC(parentMember); 745 parentChild = true; 746 childLevel = parentLevel; 747 } else { 748 childLevel = (RolapLevel) parentLevel.getChildLevel(); 749 if (childLevel == null) { 750 // member is at last level, so can have no children 751 return; 752 } 753 if (childLevel.isParentChild()) { 754 sql = makeChildMemberSql_PCRoot(parentMember); 755 parentChild = true; 756 } else { 757 sql = makeChildMemberSql(parentMember, dataSource, constraint); 758 parentChild = false; 759 } 760 } 761 SqlStatement stmt = 762 RolapUtil.executeQuery( 763 dataSource, sql, "SqlMemberSource.getMemberChildren", 764 "while building member cache"); 765 try { 766 767 int limit = MondrianProperties.instance().ResultLimit.get(); 768 boolean checkCacheStatus = true; 769 770 ResultSet resultSet = stmt.getResultSet(); 771 while (resultSet.next()) { 772 ++stmt.rowCount; 773 if (limit > 0 && limit < stmt.rowCount) { 774 // result limit exceeded, throw an exception 775 throw MondrianResource.instance().MemberFetchLimitExceeded. 776 ex(limit); 777 } 778 779 Object value = resultSet.getObject(1); 780 if (value == null) { 781 value = RolapUtil.sqlNullValue; 782 } 783 Object captionValue; 784 int columnOffset; 785 if (childLevel.hasCaptionColumn()) { 786 // The columnOffset needs to take into account 787 // the caption column if one exists 788 columnOffset = 2; 789 captionValue = resultSet.getObject(columnOffset); 790 } else { 791 columnOffset = 1; 792 captionValue = null; 793 } 794 Object key = cache.makeKey(parentMember, value); 795 RolapMember member = cache.getMember(key, checkCacheStatus); 796 checkCacheStatus = false; /* Only check the first time */ 797 if (member == null) { 798 member = makeMember( 799 parentMember, childLevel, value, captionValue, 800 parentChild, resultSet, key, columnOffset); 801 } 802 if (value == RolapUtil.sqlNullValue) { 803 children.toArray(); 804 addAsOldestSibling(children, member); 805 } else { 806 children.add(member); 807 } 808 } 809 } catch (SQLException e) { 810 throw stmt.handle(e); 811 } finally { 812 stmt.close(); 813 } 814 } 815 816 public RolapMember makeMember( 817 RolapMember parentMember, 818 RolapLevel childLevel, 819 Object value, 820 Object captionValue, 821 boolean parentChild, 822 ResultSet resultSet, 823 Object key, 824 int columnOffset) 825 throws SQLException { 826 827 RolapMember member = new RolapMember(parentMember, childLevel, value); 828 if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) { 829 member.setOrdinal(lastOrdinal++); 830 } 831 if (captionValue != null) { 832 member.setCaption(captionValue.toString()); 833 } 834 if (parentChild) { 835 // Create a 'public' and a 'data' member. The public member is 836 // calculated, and its value is the aggregation of the data member 837 // and all of the children. The children and the data member belong 838 // to the parent member; the data member does not have any 839 // children. 840 final RolapParentChildMember parentChildMember = 841 childLevel.hasClosedPeer() ? 842 new RolapParentChildMember( 843 parentMember, childLevel, value, member) 844 : new RolapParentChildMemberNoClosure( 845 parentMember, childLevel, value, member); 846 847 member = parentChildMember; 848 } 849 Property[] properties = childLevel.getProperties(); 850 if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) { 851 if (assignOrderKeys) { 852 Object orderKey = resultSet.getObject(columnOffset + 1); 853 setOrderKey(member, orderKey); 854 } 855 ++columnOffset; 856 } 857 for (int j = 0; j < properties.length; j++) { 858 Property property = properties[j]; 859 member.setProperty( 860 property.getName(), 861 resultSet.getObject(columnOffset + j + 1)); 862 } 863 cache.putMember(key, member); 864 return member; 865 } 866 867 /** 868 * Generates the SQL to find all root members of a parent-child hierarchy. 869 * For example, <blockquote> 870 * 871 * <pre>SELECT "employee_id" 872 * FROM "employee" 873 * WHERE "supervisor_id" IS NULL 874 * GROUP BY "employee_id"</pre> 875 * </blockquote> retrieves the root members of the <code>[Employee]</code> 876 * hierarchy. 877 * 878 * <p>Currently, parent-child hierarchies may have only one level (plus the 879 * 'All' level). 880 */ 881 private String makeChildMemberSql_PCRoot(RolapMember member) { 882 SqlQuery sqlQuery = 883 SqlQuery.newQuery( 884 dataSource, 885 "while generating query to retrieve children of parent/child " + 886 "hierarchy member " + member); 887 Util.assertTrue( 888 member.isAll(), 889 "In the current implementation, parent/child hierarchies must " + 890 "have only one level (plus the 'All' level)."); 891 892 RolapLevel level = (RolapLevel) member.getLevel().getChildLevel(); 893 894 Util.assertTrue(!level.isAll(), "all level cannot be parent-child"); 895 Util.assertTrue(level.isUnique(), "parent-child level '" 896 + level + "' must be unique"); 897 898 hierarchy.addToFrom(sqlQuery, level.getParentExp()); 899 String parentId = level.getParentExp().getExpression(sqlQuery); 900 StringBuilder condition = new StringBuilder(64); 901 condition.append(parentId); 902 if (level.getNullParentValue() == null || 903 level.getNullParentValue().equalsIgnoreCase("NULL")) { 904 condition.append(" IS NULL"); 905 } else { 906 // Quote the value if it doesn't seem to be a number. 907 try { 908 Util.discard(Double.parseDouble(level.getNullParentValue())); 909 condition.append(" = "); 910 condition.append(level.getNullParentValue()); 911 } catch (NumberFormatException e) { 912 condition.append(" = "); 913 Util.singleQuoteString(level.getNullParentValue(), condition); 914 } 915 } 916 sqlQuery.addWhere(condition.toString()); 917 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 918 String childId = level.getKeyExp().getExpression(sqlQuery); 919 sqlQuery.addSelect(childId); 920 sqlQuery.addGroupBy(childId); 921 hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); 922 String orderBy = level.getOrdinalExp().getExpression(sqlQuery); 923 sqlQuery.addOrderBy(orderBy, true, false, true); 924 if (!orderBy.equals(childId)) { 925 sqlQuery.addGroupBy(orderBy); 926 sqlQuery.addSelect(orderBy); 927 } 928 929 RolapProperty[] properties = level.getProperties(); 930 for (RolapProperty property : properties) { 931 final MondrianDef.Expression exp = property.getExp(); 932 hierarchy.addToFrom(sqlQuery, exp); 933 final String s = exp.getExpression(sqlQuery); 934 sqlQuery.addSelect(s); 935 sqlQuery.addGroupBy(s); 936 } 937 return sqlQuery.toString(); 938 } 939 940 /** 941 * Generates the SQL statement to access the children of 942 * <code>member</code> in a parent-child hierarchy. For example, 943 * <blockquote> 944 * 945 * <pre>SELECT "employee_id" 946 * FROM "employee" 947 * WHERE "supervisor_id" = 5</pre> 948 * </blockquote> retrieves the children of the member 949 * <code>[Employee].[5]</code>. 950 * 951 * <p>See also {@link SqlTupleReader#makeLevelMembersSql}. 952 */ 953 private String makeChildMemberSqlPC(RolapMember member) { 954 SqlQuery sqlQuery = 955 SqlQuery.newQuery( 956 dataSource, 957 "while generating query to retrieve children of " + 958 "parent/child hierarchy member " + member); 959 RolapLevel level = member.getLevel(); 960 961 Util.assertTrue(!level.isAll(), "all level cannot be parent-child"); 962 Util.assertTrue(level.isUnique(), "parent-child level '" 963 + level + "' must be unique"); 964 965 hierarchy.addToFrom(sqlQuery, level.getParentExp()); 966 String parentId = level.getParentExp().getExpression(sqlQuery); 967 968 StringBuilder buf = new StringBuilder(); 969 sqlQuery.getDialect().quote(buf, member.getKey(), level.getDatatype()); 970 sqlQuery.addWhere(parentId, " = ", buf.toString()); 971 972 hierarchy.addToFrom(sqlQuery, level.getKeyExp()); 973 String childId = level.getKeyExp().getExpression(sqlQuery); 974 sqlQuery.addSelect(childId); 975 sqlQuery.addGroupBy(childId); 976 hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); 977 String orderBy = level.getOrdinalExp().getExpression(sqlQuery); 978 sqlQuery.addOrderBy(orderBy, true, false, true); 979 if (!orderBy.equals(childId)) { 980 sqlQuery.addGroupBy(orderBy); 981 sqlQuery.addSelect(orderBy); 982 } 983 984 RolapProperty[] properties = level.getProperties(); 985 for (RolapProperty property : properties) { 986 final MondrianDef.Expression exp = property.getExp(); 987 hierarchy.addToFrom(sqlQuery, exp); 988 final String s = exp.getExpression(sqlQuery); 989 sqlQuery.addSelect(s); 990 sqlQuery.addGroupBy(s); 991 } 992 return sqlQuery.toString(); 993 } 994 995 // implement MemberReader 996 public RolapMember getLeadMember(RolapMember member, int n) { 997 throw new UnsupportedOperationException(); 998 } 999 1000 public void getMemberRange( 1001 RolapLevel level, 1002 RolapMember startMember, 1003 RolapMember endMember, 1004 List<RolapMember> memberList) 1005 { 1006 throw new UnsupportedOperationException(); 1007 } 1008 1009 public int compare( 1010 RolapMember m1, 1011 RolapMember m2, 1012 boolean siblingsAreEqual) 1013 { 1014 throw new UnsupportedOperationException(); 1015 } 1016 1017 1018 public TupleReader.MemberBuilder getMemberBuilder() { 1019 return this; 1020 } 1021 1022 public RolapMember getDefaultMember() { 1023 // we expected the CacheMemberReader to implement this 1024 throw new UnsupportedOperationException(); 1025 } 1026 1027 public RolapMember getMemberParent(RolapMember member) { 1028 throw new UnsupportedOperationException(); 1029 } 1030 1031 // ~ -- Inner classes ------------------------------------------------------ 1032 1033 /** 1034 * Member of a parent-child dimension which has a closure table. 1035 * 1036 * <p>When looking up cells, this member will automatically be converted 1037 * to a corresponding member of the auxiliary dimension which maps onto 1038 * the closure table. 1039 */ 1040 private static class RolapParentChildMember extends RolapMember { 1041 private final RolapMember dataMember; 1042 private int depth = 0; 1043 1044 public RolapParentChildMember( 1045 RolapMember parentMember, 1046 RolapLevel childLevel, 1047 Object value, 1048 RolapMember dataMember) 1049 { 1050 super(parentMember, childLevel, value); 1051 this.dataMember = dataMember; 1052 this.depth = (parentMember != null) 1053 ? parentMember.getDepth() + 1 1054 : 0; 1055 } 1056 1057 public Member getDataMember() { 1058 return dataMember; 1059 } 1060 1061 public Object getPropertyValue(String propertyName, boolean matchCase) { 1062 if (Util.equal( 1063 propertyName, Property.CONTRIBUTING_CHILDREN.name, matchCase)) 1064 { 1065 List<RolapMember> list = new ArrayList<RolapMember>(); 1066 list.add(dataMember); 1067 RolapHierarchy hierarchy = getHierarchy(); 1068 hierarchy.getMemberReader().getMemberChildren(dataMember, list); 1069 return list; 1070 } else { 1071 return super.getPropertyValue(propertyName, matchCase); 1072 } 1073 } 1074 1075 /** 1076 * @return the members's depth 1077 * @see mondrian.olap.Member#getDepth() 1078 */ 1079 public int getDepth() { 1080 return depth; 1081 } 1082 1083 public int getOrdinal() { 1084 return dataMember.getOrdinal(); 1085 } 1086 } 1087 1088 /** 1089 * Member of a parent-child dimension which has no closure table. 1090 * 1091 * <p>This member is calculated. When you ask for its value, it returns 1092 * an expression which aggregates the values of its child members. 1093 * This calculation is very inefficient, and we can only support 1094 * aggregatable measures ("count distinct" is non-aggregatable). 1095 * Unfortunately it's the best we can do without a closure table. 1096 */ 1097 private static class RolapParentChildMemberNoClosure 1098 extends RolapParentChildMember { 1099 1100 public RolapParentChildMemberNoClosure( 1101 RolapMember parentMember, 1102 RolapLevel childLevel, Object value, RolapMember dataMember) 1103 { 1104 super(parentMember, childLevel, value, dataMember); 1105 } 1106 1107 protected boolean computeCalculated(final MemberType memberType) { 1108 return true; 1109 } 1110 1111 public Exp getExpression() { 1112 return getHierarchy().getAggregateChildrenExpression(); 1113 } 1114 } 1115 } 1116 1117 // End SqlMemberSource.java