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-2012 Pentaho and others 009// All Rights Reserved. 010// 011// jhyde, 12 August, 2001 012*/ 013package mondrian.rolap; 014 015import mondrian.olap.*; 016import mondrian.resource.MondrianResource; 017import mondrian.rolap.agg.*; 018import mondrian.rolap.aggmatcher.AggStar; 019import mondrian.rolap.sql.SqlQuery; 020import mondrian.server.Locus; 021import mondrian.spi.*; 022import mondrian.util.Bug; 023 024import org.apache.commons.collections.map.ReferenceMap; 025import org.apache.log4j.Logger; 026 027import java.io.PrintWriter; 028import java.io.StringWriter; 029import java.lang.ref.SoftReference; 030import java.sql.Connection; 031import java.sql.*; 032import java.util.*; 033 034import javax.sql.DataSource; 035 036/** 037 * A <code>RolapStar</code> is a star schema. It is the means to read cell 038 * values. 039 * 040 * <p>todo: Move this class into a package that specializes in relational 041 * aggregation, doesn't know anything about hierarchies etc. 042 * 043 * @author jhyde 044 * @since 12 August, 2001 045 */ 046public class RolapStar { 047 private static final Logger LOGGER = Logger.getLogger(RolapStar.class); 048 049 private final RolapSchema schema; 050 051 // not final for test purposes 052 private DataSource dataSource; 053 054 private final Table factTable; 055 056 /** 057 * Number of columns (column and columnName). 058 */ 059 private int columnCount; 060 061 /** 062 * Keeps track of the columns across all tables. Should have 063 * a number of elements equal to columnCount. 064 */ 065 private final List<Column> columnList = new ArrayList<Column>(); 066 067 private final Dialect sqlQueryDialect; 068 069 /** 070 * If true, then database aggregation information is cached, otherwise 071 * it is flushed after each query. 072 */ 073 private boolean cacheAggregations; 074 075 /** 076 * Partially ordered list of AggStars associated with this RolapStar's fact 077 * table. 078 */ 079 private final List<AggStar> aggStars = new LinkedList<AggStar>(); 080 081 private DataSourceChangeListener changeListener; 082 083 // temporary model, should eventually use RolapStar.Table and 084 // RolapStar.Column 085 private StarNetworkNode factNode; 086 private Map<String, StarNetworkNode> nodeLookup = 087 new HashMap<String, StarNetworkNode>(); 088 089 private final RolapStatisticsCache statisticsCache; 090 091 /** 092 * Creates a RolapStar. Please use 093 * {@link RolapSchema.RolapStarRegistry#getOrCreateStar} to create a 094 * {@link RolapStar}. 095 */ 096 RolapStar( 097 final RolapSchema schema, 098 final DataSource dataSource, 099 final MondrianDef.Relation fact) 100 { 101 this.cacheAggregations = true; 102 this.schema = schema; 103 this.dataSource = dataSource; 104 this.factTable = new RolapStar.Table(this, fact, null, null); 105 106 // phase out and replace with Table, Column network 107 this.factNode = 108 new StarNetworkNode(null, factTable.alias, null, null, null); 109 110 this.sqlQueryDialect = schema.getDialect(); 111 this.changeListener = schema.getDataSourceChangeListener(); 112 this.statisticsCache = new RolapStatisticsCache(this); 113 } 114 115 /** 116 * Retrieves the value of the cell identified by a cell request, if it 117 * can be found in the local cache of the current statement (thread). 118 * 119 * <p>If it is not in the local cache, returns null. The client's next 120 * step will presumably be to request a segment that contains the cell 121 * from the global cache, external cache, or by issuing a SQL statement. 122 * 123 * <p>Returns {@link Util#nullValue} if a segment contains the cell and the 124 * cell's value is null. 125 * 126 * <p>If <code>pinSet</code> is not null, pins the segment that holds it 127 * into the local cache. <code>pinSet</code> ensures that a segment is 128 * only pinned once. 129 * 130 * @param request Cell request 131 * 132 * @param pinSet Set into which to pin the segment; or null 133 * 134 * @return Cell value, or {@link Util#nullValue} if the cell value is null, 135 * or null if the cell is not in any segment in the local cache. 136 */ 137 public Object getCellFromCache( 138 CellRequest request, 139 RolapAggregationManager.PinSet pinSet) 140 { 141 // REVIEW: Is it possible to optimize this so not every cell lookup 142 // causes an AggregationKey to be created? 143 AggregationKey aggregationKey = new AggregationKey(request); 144 145 final Bar bar = localBars.get(); 146 for (SegmentWithData segment : Util.GcIterator.over(bar.segmentRefs)) { 147 if (!segment.getConstrainedColumnsBitKey().equals( 148 request.getConstrainedColumnsBitKey())) 149 { 150 continue; 151 } 152 153 if (!segment.matches(aggregationKey, request.getMeasure())) { 154 continue; 155 } 156 157 Object o = segment.getCellValue(request.getSingleValues()); 158 if (o != null) { 159 if (pinSet != null) { 160 ((AggregationManager.PinSetImpl) pinSet).add(segment); 161 } 162 return o; 163 } 164 } 165 // No segment contains the requested cell. 166 return null; 167 } 168 169 public Object getCellFromAllCaches(final CellRequest request) { 170 // First, try the local/thread cache. 171 Object result = getCellFromCache(request, null); 172 if (result != null) { 173 return result; 174 } 175 // Now ask the segment cache manager. 176 return getCellFromExternalCache(request); 177 } 178 179 private Object getCellFromExternalCache(CellRequest request) { 180 final SegmentWithData segment = 181 Locus.peek().getServer().getAggregationManager() 182 .cacheMgr.peek(request); 183 if (segment == null) { 184 return null; 185 } 186 return segment.getCellValue(request.getSingleValues()); 187 } 188 189 public void register(SegmentWithData segment) { 190 localBars.get().segmentRefs.add( 191 new SoftReference<SegmentWithData>(segment)); 192 } 193 194 public RolapStatisticsCache getStatisticsCache() { 195 return statisticsCache; 196 } 197 198 /** 199 * Temporary. Contains the local cache for a particular thread. Because 200 * it is accessed via a thread-local, the data structures can be accessed 201 * without acquiring locks. 202 * 203 * @see Util#deprecated(Object) 204 */ 205 public static class Bar { 206 /** Holds all thread-local aggregations of this star. */ 207 private final Map<AggregationKey, Aggregation> aggregations = 208 new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK); 209 210 private final List<SoftReference<SegmentWithData>> segmentRefs = 211 new ArrayList<SoftReference<SegmentWithData>>(); 212 } 213 214 private final ThreadLocal<Bar> localBars = 215 new ThreadLocal<Bar>() { 216 protected Bar initialValue() { 217 return new Bar(); 218 } 219 }; 220 221 private static class StarNetworkNode { 222 private StarNetworkNode parent; 223 private MondrianDef.Relation origRel; 224 private String foreignKey; 225 private String joinKey; 226 227 private StarNetworkNode( 228 StarNetworkNode parent, 229 String alias, 230 MondrianDef.Relation origRel, 231 String foreignKey, 232 String joinKey) 233 { 234 this.parent = parent; 235 this.origRel = origRel; 236 this.foreignKey = foreignKey; 237 this.joinKey = joinKey; 238 } 239 240 private boolean isCompatible( 241 StarNetworkNode compatibleParent, 242 MondrianDef.Relation rel, 243 String compatibleForeignKey, 244 String compatibleJoinKey) 245 { 246 return parent == compatibleParent 247 && origRel.getClass().equals(rel.getClass()) 248 && foreignKey.equals(compatibleForeignKey) 249 && joinKey.equals(compatibleJoinKey); 250 } 251 } 252 253 protected MondrianDef.RelationOrJoin cloneRelation( 254 MondrianDef.Relation rel, 255 String possibleName) 256 { 257 if (rel instanceof MondrianDef.Table) { 258 MondrianDef.Table tbl = (MondrianDef.Table)rel; 259 return new MondrianDef.Table( 260 tbl, 261 possibleName); 262 } else if (rel instanceof MondrianDef.View) { 263 MondrianDef.View view = (MondrianDef.View)rel; 264 MondrianDef.View newView = new MondrianDef.View(view); 265 newView.alias = possibleName; 266 return newView; 267 } else if (rel instanceof MondrianDef.InlineTable) { 268 MondrianDef.InlineTable inlineTable = 269 (MondrianDef.InlineTable) rel; 270 MondrianDef.InlineTable newInlineTable = 271 new MondrianDef.InlineTable(inlineTable); 272 newInlineTable.alias = possibleName; 273 return newInlineTable; 274 } else { 275 throw new UnsupportedOperationException(); 276 } 277 } 278 279 /** 280 * Generates a unique relational join to the fact table via re-aliasing 281 * MondrianDef.Relations 282 * 283 * currently called in the RolapCubeHierarchy constructor. This should 284 * eventually be phased out and replaced with RolapStar.Table and 285 * RolapStar.Column references 286 * 287 * @param rel the relation needing uniqueness 288 * @param factForeignKey the foreign key of the fact table 289 * @param primaryKey the join key of the relation 290 * @param primaryKeyTable the join table of the relation 291 * @return if necessary a new relation that has been re-aliased 292 */ 293 public MondrianDef.RelationOrJoin getUniqueRelation( 294 MondrianDef.RelationOrJoin rel, 295 String factForeignKey, 296 String primaryKey, 297 String primaryKeyTable) 298 { 299 return getUniqueRelation( 300 factNode, rel, factForeignKey, primaryKey, primaryKeyTable); 301 } 302 303 private MondrianDef.RelationOrJoin getUniqueRelation( 304 StarNetworkNode parent, 305 MondrianDef.RelationOrJoin relOrJoin, 306 String foreignKey, 307 String joinKey, 308 String joinKeyTable) 309 { 310 if (relOrJoin == null) { 311 return null; 312 } else if (relOrJoin instanceof MondrianDef.Relation) { 313 int val = 0; 314 MondrianDef.Relation rel = 315 (MondrianDef.Relation) relOrJoin; 316 String newAlias = 317 joinKeyTable != null ? joinKeyTable : rel.getAlias(); 318 while (true) { 319 StarNetworkNode node = nodeLookup.get(newAlias); 320 if (node == null) { 321 if (val != 0) { 322 rel = (MondrianDef.Relation) 323 cloneRelation(rel, newAlias); 324 } 325 node = 326 new StarNetworkNode( 327 parent, newAlias, rel, foreignKey, joinKey); 328 nodeLookup.put(newAlias, node); 329 return rel; 330 } else if (node.isCompatible( 331 parent, rel, foreignKey, joinKey)) 332 { 333 return node.origRel; 334 } 335 newAlias = rel.getAlias() + "_" + (++val); 336 } 337 } else if (relOrJoin instanceof MondrianDef.Join) { 338 // determine if the join starts from the left or right side 339 MondrianDef.Join join = (MondrianDef.Join)relOrJoin; 340 if (join.left instanceof MondrianDef.Join) { 341 throw MondrianResource.instance().IllegalLeftDeepJoin.ex(); 342 } 343 final MondrianDef.RelationOrJoin left; 344 final MondrianDef.RelationOrJoin right; 345 if (join.getLeftAlias().equals(joinKeyTable)) { 346 // first manage left then right 347 left = 348 getUniqueRelation( 349 parent, join.left, foreignKey, 350 joinKey, joinKeyTable); 351 parent = nodeLookup.get( 352 ((MondrianDef.Relation) left).getAlias()); 353 right = 354 getUniqueRelation( 355 parent, join.right, join.leftKey, 356 join.rightKey, join.getRightAlias()); 357 } else if (join.getRightAlias().equals(joinKeyTable)) { 358 // right side must equal 359 right = 360 getUniqueRelation( 361 parent, join.right, foreignKey, 362 joinKey, joinKeyTable); 363 parent = nodeLookup.get( 364 ((MondrianDef.Relation) right).getAlias()); 365 left = 366 getUniqueRelation( 367 parent, join.left, join.rightKey, 368 join.leftKey, join.getLeftAlias()); 369 } else { 370 throw new MondrianException( 371 "failed to match primary key table to join tables"); 372 } 373 374 if (join.left != left || join.right != right) { 375 join = 376 new MondrianDef.Join( 377 left instanceof MondrianDef.Relation 378 ? ((MondrianDef.Relation) left).getAlias() 379 : null, 380 join.leftKey, 381 left, 382 right instanceof MondrianDef.Relation 383 ? ((MondrianDef.Relation) right).getAlias() 384 : null, 385 join.rightKey, 386 right); 387 } 388 return join; 389 } 390 return null; 391 } 392 393 /** 394 * Returns this RolapStar's column count. After a star has been created with 395 * all of its columns, this is the number of columns in the star. 396 */ 397 public int getColumnCount() { 398 return columnCount; 399 } 400 401 /** 402 * This is used by the {@link Column} constructor to get a unique id (per 403 * its parent {@link RolapStar}). 404 */ 405 private int nextColumnCount() { 406 return columnCount++; 407 } 408 409 /** 410 * Decrements the column counter; used if a newly 411 * created column is found to already exist. 412 */ 413 private int decrementColumnCount() { 414 return columnCount--; 415 } 416 417 /** 418 * Place holder in case in the future we wish to be able to 419 * reload aggregates. In that case, if aggregates had already been loaded, 420 * i.e., this star has some aggstars, then those aggstars are cleared. 421 */ 422 public void prepareToLoadAggregates() { 423 aggStars.clear(); 424 } 425 426 /** 427 * Adds an {@link AggStar} to this star. 428 * 429 * <p>Internally the AggStars are added in sort order, smallest row count 430 * to biggest, so that the most efficient AggStar is encountered first; 431 * ties do not matter. 432 */ 433 public void addAggStar(AggStar aggStar) { 434 // Add it before the first AggStar which is larger, if there is one. 435 int size = aggStar.getSize(); 436 ListIterator<AggStar> lit = aggStars.listIterator(); 437 while (lit.hasNext()) { 438 AggStar as = lit.next(); 439 if (as.getSize() >= size) { 440 lit.previous(); 441 lit.add(aggStar); 442 return; 443 } 444 } 445 446 // There is no larger star. Add at the end of the list. 447 aggStars.add(aggStar); 448 } 449 450 /** 451 * Clears the list of agg stars. 452 */ 453 void clearAggStarList() { 454 aggStars.clear(); 455 } 456 457 /** 458 * Reorder the list of aggregate stars. This should be called if the 459 * algorithm used to order the AggStars has been changed. 460 */ 461 public void reOrderAggStarList() { 462 List<AggStar> oldList = new ArrayList<AggStar>(aggStars); 463 aggStars.clear(); 464 for (AggStar aggStar : oldList) { 465 addAggStar(aggStar); 466 } 467 } 468 469 /** 470 * Returns this RolapStar's aggregate table AggStars, ordered in ascending 471 * order of size. 472 */ 473 public List<AggStar> getAggStars() { 474 return aggStars; 475 } 476 477 /** 478 * Returns the fact table at the center of this RolapStar. 479 * 480 * @return fact table 481 */ 482 public Table getFactTable() { 483 return factTable; 484 } 485 486 /** 487 * Clones an existing SqlQuery to create a new one (this cloning creates one 488 * with an empty sql query). 489 */ 490 public SqlQuery getSqlQuery() { 491 return new SqlQuery(getSqlQueryDialect()); 492 } 493 494 /** 495 * Returns this RolapStar's SQL dialect. 496 */ 497 public Dialect getSqlQueryDialect() { 498 return sqlQueryDialect; 499 } 500 501 /** 502 * Sets whether to cache database aggregation information; if false, cache 503 * is flushed after each query. 504 * 505 * <p>This method is called only by the RolapCube and is only called if 506 * caching is to be turned off. Note that the same RolapStar can be 507 * associated with more than on RolapCube. If any one of those cubes has 508 * caching turned off, then caching is turned off for all of them. 509 * 510 * @param cacheAggregations Whether to cache database aggregation 511 */ 512 void setCacheAggregations(boolean cacheAggregations) { 513 // this can only change from true to false 514 this.cacheAggregations = cacheAggregations; 515 clearCachedAggregations(false); 516 } 517 518 /** 519 * Returns whether the this RolapStar cache aggregates. 520 * 521 * @see #setCacheAggregations(boolean) 522 */ 523 boolean isCacheAggregations() { 524 return this.cacheAggregations; 525 } 526 527 boolean isCacheDisabled() { 528 return MondrianProperties.instance().DisableCaching.get(); 529 } 530 531 /** 532 * Clears the aggregate cache. This only does something if aggregate caching 533 * is disabled (see {@link #setCacheAggregations(boolean)}). 534 * 535 * @param forced If true, clears cached aggregations regardless of any other 536 * settings. If false, clears only cache from the current thread 537 */ 538 void clearCachedAggregations(boolean forced) { 539 if (forced || !cacheAggregations || isCacheDisabled()) { 540 if (LOGGER.isDebugEnabled()) { 541 StringBuilder buf = new StringBuilder(100); 542 buf.append("RolapStar.clearCachedAggregations: schema="); 543 buf.append(schema.getName()); 544 buf.append(", star="); 545 buf.append(getFactTable().getAlias()); 546 LOGGER.debug(buf.toString()); 547 } 548 549 // Clear aggregation cache for the current thread context. 550 localBars.get().aggregations.clear(); 551 localBars.get().segmentRefs.clear(); 552 } 553 } 554 555 /** 556 * Looks up an aggregation or creates one if it does not exist in an 557 * atomic (synchronized) operation. 558 * 559 * <p>When a new aggregation is created, it is marked as thread local. 560 * 561 * @param aggregationKey this is the constrained column bitkey 562 */ 563 public Aggregation lookupOrCreateAggregation( 564 AggregationKey aggregationKey) 565 { 566 Aggregation aggregation = lookupSegment(aggregationKey); 567 if (aggregation != null) { 568 return aggregation; 569 } 570 571 aggregation = 572 new Aggregation( 573 aggregationKey); 574 575 localBars.get().aggregations.put( 576 aggregationKey, aggregation); 577 578 // Let the change listener get the opportunity to register the 579 // first time the aggregation is used 580 if (this.cacheAggregations 581 && !isCacheDisabled() 582 && changeListener != null) 583 { 584 Util.discard( 585 changeListener.isAggregationChanged(aggregationKey)); 586 } 587 return aggregation; 588 } 589 590 /** 591 * Looks for an existing aggregation over a given set of columns, in the 592 * local segment cache, returning <code>null</code> if there is none. 593 * 594 * <p>Must be called from synchronized context. 595 * 596 * @see Util#deprecated(Object) currently always returns null -- remove 597 */ 598 public Aggregation lookupSegment(AggregationKey aggregationKey) { 599 return localBars.get().aggregations.get(aggregationKey); 600 } 601 602 /** For testing purposes only. */ 603 public void setDataSource(DataSource dataSource) { 604 this.dataSource = dataSource; 605 } 606 607 /** 608 * Returns the DataSource used to connect to the underlying DBMS. 609 * 610 * @return DataSource 611 */ 612 public DataSource getDataSource() { 613 return dataSource; 614 } 615 616 /** 617 * Retrieves the {@link RolapStar.Measure} in which a measure is stored. 618 */ 619 public static Measure getStarMeasure(Member member) { 620 return (Measure) ((RolapStoredMeasure) member).getStarMeasure(); 621 } 622 623 /** 624 * Retrieves a named column, returns null if not found. 625 */ 626 public Column[] lookupColumns(String tableAlias, String columnName) { 627 final Table table = factTable.findDescendant(tableAlias); 628 return (table == null) ? null : table.lookupColumns(columnName); 629 } 630 631 /** 632 * This is used by TestAggregationManager only. 633 */ 634 public Column lookupColumn(String tableAlias, String columnName) { 635 final Table table = factTable.findDescendant(tableAlias); 636 return (table == null) ? null : table.lookupColumn(columnName); 637 } 638 639 public BitKey getBitKey(String[] tableAlias, String[] columnName) { 640 BitKey bitKey = BitKey.Factory.makeBitKey(getColumnCount()); 641 Column starColumn; 642 for (int i = 0; i < tableAlias.length; i ++) { 643 starColumn = lookupColumn(tableAlias[i], columnName[i]); 644 if (starColumn != null) { 645 bitKey.set(starColumn.getBitPosition()); 646 } 647 } 648 return bitKey; 649 } 650 651 /** 652 * Returns a list of all aliases used in this star. 653 */ 654 public List<String> getAliasList() { 655 List<String> aliasList = new ArrayList<String>(); 656 if (factTable != null) { 657 collectAliases(aliasList, factTable); 658 } 659 return aliasList; 660 } 661 662 /** 663 * Finds all of the table aliases in a table and its children. 664 */ 665 private static void collectAliases(List<String> aliasList, Table table) { 666 aliasList.add(table.getAlias()); 667 for (Table child : table.children) { 668 collectAliases(aliasList, child); 669 } 670 } 671 672 /** 673 * Collects all columns in this table and its children. 674 * If <code>joinColumn</code> is specified, only considers child tables 675 * joined by the given column. 676 */ 677 public static void collectColumns( 678 Collection<Column> columnList, 679 Table table, 680 MondrianDef.Column joinColumn) 681 { 682 if (joinColumn == null) { 683 columnList.addAll(table.columnList); 684 } 685 for (Table child : table.children) { 686 if (joinColumn == null 687 || child.getJoinCondition().left.equals(joinColumn)) 688 { 689 collectColumns(columnList, child, null); 690 } 691 } 692 } 693 694 private boolean containsColumn(String tableName, String columnName) { 695 Connection jdbcConnection; 696 try { 697 jdbcConnection = dataSource.getConnection(); 698 } catch (SQLException e1) { 699 throw Util.newInternal( 700 e1, "Error while creating connection from data source"); 701 } 702 try { 703 final DatabaseMetaData metaData = jdbcConnection.getMetaData(); 704 final ResultSet columns = 705 metaData.getColumns(null, null, tableName, columnName); 706 return columns.next(); 707 } catch (SQLException e) { 708 throw Util.newInternal( 709 "Error while retrieving metadata for table '" + tableName 710 + "', column '" + columnName + "'"); 711 } finally { 712 try { 713 jdbcConnection.close(); 714 } catch (SQLException e) { 715 // ignore 716 } 717 } 718 } 719 720 /** 721 * Adds a column to the star's list of all columns across all tables. 722 * 723 * @param c the column to add 724 */ 725 private void addColumn(Column c) { 726 columnList.add(c.getBitPosition(), c); 727 } 728 729 /** 730 * Look up the column at the given bit position. 731 * 732 * @param bitPos bit position to look up 733 * @return column at the given position 734 */ 735 public Column getColumn(int bitPos) { 736 return columnList.get(bitPos); 737 } 738 739 public RolapSchema getSchema() { 740 return schema; 741 } 742 743 /** 744 * Generates a SQL statement to read all instances of the given attributes. 745 * 746 * <p>The SQL statement is of the form {@code SELECT ... FROM ... JOIN ... 747 * GROUP BY ...}. It is useful for populating an aggregate table. 748 * 749 * @param columnList List of columns (attributes and measures) 750 * @param columnNameList List of column names (must have same cardinality 751 * as {@code columnList}) 752 * @return SQL SELECT statement 753 */ 754 public String generateSql( 755 List<Column> columnList, 756 List<String> columnNameList) 757 { 758 final SqlQuery query = new SqlQuery(sqlQueryDialect, true); 759 query.addFrom( 760 factTable.relation, 761 factTable.relation.getAlias(), 762 false); 763 int k = -1; 764 for (Column column : columnList) { 765 ++k; 766 column.table.addToFrom(query, false, true); 767 String columnExpr = column.generateExprString(query); 768 if (column instanceof Measure) { 769 Measure measure = (Measure) column; 770 columnExpr = measure.getAggregator().getExpression(columnExpr); 771 } 772 final String columnName = columnNameList.get(k); 773 String alias = query.addSelect(columnExpr, null, columnName); 774 if (!(column instanceof Measure)) { 775 query.addGroupBy(columnExpr, alias); 776 } 777 } 778 // remove whitespace from query - in particular, the trailing newline 779 return query.toString().trim(); 780 } 781 782 public String toString() { 783 StringWriter sw = new StringWriter(256); 784 PrintWriter pw = new PrintWriter(sw); 785 print(pw, "", true); 786 pw.flush(); 787 return sw.toString(); 788 } 789 790 /** 791 * Prints the state of this <code>RolapStar</code> 792 * 793 * @param pw Writer 794 * @param prefix Prefix to print at the start of each line 795 * @param structure Whether to print the structure of the star 796 */ 797 public void print(PrintWriter pw, String prefix, boolean structure) { 798 if (structure) { 799 pw.print(prefix); 800 pw.println("RolapStar:"); 801 String subprefix = prefix + " "; 802 factTable.print(pw, subprefix); 803 804 for (AggStar aggStar : getAggStars()) { 805 aggStar.print(pw, subprefix); 806 } 807 } 808 } 809 810 /** 811 * Returns the listener for changes to this star's underlying database. 812 * 813 * @return Returns the Data source change listener. 814 */ 815 public DataSourceChangeListener getChangeListener() { 816 return changeListener; 817 } 818 819 /** 820 * Sets the listener for changes to this star's underlying database. 821 * 822 * @param changeListener The Data source change listener to set 823 */ 824 public void setChangeListener(DataSourceChangeListener changeListener) { 825 this.changeListener = changeListener; 826 } 827 828 // -- Inner classes -------------------------------------------------------- 829 830 /** 831 * A column in a star schema. 832 */ 833 public static class Column { 834 public static final Comparator<Column> COMPARATOR = 835 new Comparator<Column>() { 836 public int compare( 837 Column object1, 838 Column object2) 839 { 840 return Util.compare( 841 object1.getBitPosition(), 842 object2.getBitPosition()); 843 } 844 }; 845 846 private final Table table; 847 private final MondrianDef.Expression expression; 848 private final Dialect.Datatype datatype; 849 private final SqlStatement.Type internalType; 850 private final String name; 851 852 /** 853 * When a Column is a column, and not a Measure, the parent column 854 * is the coloumn associated with next highest Level. 855 */ 856 private final Column parentColumn; 857 858 /** 859 * This is used during both aggregate table recognition and aggregate 860 * table generation. For multiple dimension usages, multiple shared 861 * dimension or unshared dimension with the same column names, 862 * this is used to disambiguate aggregate column names. 863 */ 864 private final String usagePrefix; 865 /** 866 * This is only used in RolapAggregationManager and adds 867 * non-constraining columns making the drill-through queries easier for 868 * humans to understand. 869 */ 870 private final Column nameColumn; 871 872 private boolean isNameColumn; 873 874 /** this has a unique value per star */ 875 private final int bitPosition; 876 /** 877 * The estimated cardinality of the column. 878 * {@link Integer#MIN_VALUE} means unknown. 879 */ 880 private int approxCardinality = Integer.MIN_VALUE; 881 882 private Column( 883 String name, 884 Table table, 885 MondrianDef.Expression expression, 886 Dialect.Datatype datatype) 887 { 888 this( 889 name, table, expression, datatype, null, null, 890 null, null, Integer.MIN_VALUE, table.star.nextColumnCount()); 891 } 892 893 private Column( 894 String name, 895 Table table, 896 MondrianDef.Expression expression, 897 Dialect.Datatype datatype, 898 SqlStatement.Type internalType, 899 Column nameColumn, 900 Column parentColumn, 901 String usagePrefix, 902 int approxCardinality, 903 int bitPosition) 904 { 905 this.name = name; 906 this.table = table; 907 this.expression = expression; 908 assert expression == null 909 || expression.getGenericExpression() != null; 910 this.datatype = datatype; 911 this.internalType = internalType; 912 this.bitPosition = bitPosition; 913 this.nameColumn = nameColumn; 914 this.parentColumn = parentColumn; 915 this.usagePrefix = usagePrefix; 916 this.approxCardinality = approxCardinality; 917 if (nameColumn != null) { 918 nameColumn.isNameColumn = true; 919 } 920 if (table != null) { 921 table.star.addColumn(this); 922 } 923 } 924 925 /** 926 * Fake column. 927 * 928 * @param datatype Datatype 929 */ 930 protected Column(Dialect.Datatype datatype) 931 { 932 this( 933 null, 934 null, 935 null, 936 datatype, 937 null, 938 null, 939 null, 940 null, 941 Integer.MIN_VALUE, 942 0); 943 } 944 945 public boolean equals(Object obj) { 946 if (! (obj instanceof RolapStar.Column)) { 947 return false; 948 } 949 RolapStar.Column other = (RolapStar.Column) obj; 950 // Note: both columns have to be from the same table 951 return 952 other.table == this.table 953 && Util.equals(other.expression, this.expression) 954 && other.datatype == this.datatype 955 && other.name.equals(this.name); 956 } 957 958 public int hashCode() { 959 int h = name.hashCode(); 960 h = Util.hash(h, table); 961 return h; 962 } 963 964 public String getName() { 965 return name; 966 } 967 968 public int getBitPosition() { 969 return bitPosition; 970 } 971 972 public RolapStar getStar() { 973 return table.star; 974 } 975 976 public RolapStar.Table getTable() { 977 return table; 978 } 979 980 public SqlQuery getSqlQuery() { 981 return getTable().getStar().getSqlQuery(); 982 } 983 984 public RolapStar.Column getNameColumn() { 985 return nameColumn; 986 } 987 988 public RolapStar.Column getParentColumn() { 989 return parentColumn; 990 } 991 992 public String getUsagePrefix() { 993 return usagePrefix; 994 } 995 996 public boolean isNameColumn() { 997 return isNameColumn; 998 } 999 1000 public MondrianDef.Expression getExpression() { 1001 return expression; 1002 } 1003 1004 /** 1005 * Generates a SQL expression, which typically this looks like 1006 * this: <code><i>tableName</i>.<i>columnName</i></code>. 1007 */ 1008 public String generateExprString(SqlQuery query) { 1009 return getExpression().getExpression(query); 1010 } 1011 1012 /** 1013 * Get column cardinality from the schema cache if possible; 1014 * otherwise issue a select count(distinct) query to retrieve 1015 * the cardinality and stores it in the cache. 1016 * 1017 * @return the column cardinality. 1018 */ 1019 public int getCardinality() { 1020 if (approxCardinality < 0) { 1021 approxCardinality = 1022 table.star.getStatisticsCache().getColumnCardinality( 1023 table.relation, expression, approxCardinality); 1024 } 1025 return approxCardinality; 1026 } 1027 1028 /** 1029 * Generates a predicate that a column matches one of a list of values. 1030 * 1031 * <p> 1032 * Several possible outputs, depending upon whether the there are 1033 * nulls:<ul> 1034 * 1035 * <li>One not-null value: <code>foo.bar = 1</code> 1036 * 1037 * <li>All values not null: <code>foo.bar in (1, 2, 3)</code></li 1038 * 1039 * <li>Null and not null values: 1040 * <code>(foo.bar is null or foo.bar in (1, 2))</code></li> 1041 * 1042 * <li>Only null values: 1043 * <code>foo.bar is null</code></li> 1044 * 1045 * <li>String values: <code>foo.bar in ('a', 'b', 'c')</code></li> 1046 * 1047 * </ul> 1048 */ 1049 public static String createInExpr( 1050 final String expr, 1051 StarColumnPredicate predicate, 1052 Dialect.Datatype datatype, 1053 SqlQuery sqlQuery) 1054 { 1055 // Sometimes a column predicate is created without a column. This 1056 // is unfortunate, and we will fix it some day. For now, create 1057 // a fake column with all of the information needed by the toSql 1058 // method, and a copy of the predicate wrapping that fake column. 1059 if (!Bug.BugMondrian313Fixed 1060 || !Bug.BugMondrian314Fixed 1061 && predicate.getConstrainedColumn() == null) 1062 { 1063 Column column = new Column(datatype) { 1064 public String generateExprString(SqlQuery query) { 1065 return expr; 1066 } 1067 }; 1068 predicate = predicate.cloneWithColumn(column); 1069 } 1070 1071 StringBuilder buf = new StringBuilder(64); 1072 predicate.toSql(sqlQuery, buf); 1073 return buf.toString(); 1074 } 1075 1076 public String toString() { 1077 StringWriter sw = new StringWriter(256); 1078 PrintWriter pw = new PrintWriter(sw); 1079 print(pw, ""); 1080 pw.flush(); 1081 return sw.toString(); 1082 } 1083 1084 /** 1085 * Prints this column. 1086 * 1087 * @param pw Print writer 1088 * @param prefix Prefix to print first, such as spaces for indentation 1089 */ 1090 public void print(PrintWriter pw, String prefix) { 1091 SqlQuery sqlQuery = getSqlQuery(); 1092 pw.print(prefix); 1093 pw.print(getName()); 1094 pw.print(" ("); 1095 pw.print(getBitPosition()); 1096 pw.print("): "); 1097 pw.print(generateExprString(sqlQuery)); 1098 } 1099 1100 public Dialect.Datatype getDatatype() { 1101 return datatype; 1102 } 1103 1104 /** 1105 * Returns a string representation of the datatype of this column, in 1106 * the dialect specified. For example, 'DECIMAL(10, 2) NOT NULL'. 1107 * 1108 * @param dialect Dialect 1109 * @return String representation of column's datatype 1110 */ 1111 public String getDatatypeString(Dialect dialect) { 1112 final SqlQuery query = new SqlQuery(dialect); 1113 query.addFrom( 1114 table.star.factTable.relation, table.star.factTable.alias, 1115 false); 1116 query.addFrom(table.relation, table.alias, false); 1117 query.addSelect(expression.getExpression(query), null); 1118 final String sql = query.toString(); 1119 Connection jdbcConnection = null; 1120 try { 1121 jdbcConnection = table.star.dataSource.getConnection(); 1122 final PreparedStatement pstmt = 1123 jdbcConnection.prepareStatement(sql); 1124 final ResultSetMetaData resultSetMetaData = 1125 pstmt.getMetaData(); 1126 assert resultSetMetaData.getColumnCount() == 1; 1127 final String type = resultSetMetaData.getColumnTypeName(1); 1128 int precision = resultSetMetaData.getPrecision(1); 1129 final int scale = resultSetMetaData.getScale(1); 1130 if (type.equals("DOUBLE")) { 1131 precision = 0; 1132 } 1133 String typeString; 1134 if (precision == 0) { 1135 typeString = type; 1136 } else if (scale == 0) { 1137 typeString = type + "(" + precision + ")"; 1138 } else { 1139 typeString = type + "(" + precision + ", " + scale + ")"; 1140 } 1141 pstmt.close(); 1142 jdbcConnection.close(); 1143 jdbcConnection = null; 1144 return typeString; 1145 } catch (SQLException e) { 1146 throw Util.newError( 1147 e, 1148 "Error while deriving type of column " + toString()); 1149 } finally { 1150 if (jdbcConnection != null) { 1151 try { 1152 jdbcConnection.close(); 1153 } catch (SQLException e) { 1154 // ignore 1155 } 1156 } 1157 } 1158 } 1159 1160 public SqlStatement.Type getInternalType() { 1161 return internalType; 1162 } 1163 } 1164 1165 /** 1166 * Definition of a measure in a star schema. 1167 * 1168 * <p>A measure is basically just a column; except that its 1169 * {@link #aggregator} defines how it is to be rolled up. 1170 */ 1171 public static class Measure extends Column { 1172 private final String cubeName; 1173 private final RolapAggregator aggregator; 1174 1175 public Measure( 1176 String name, 1177 String cubeName, 1178 RolapAggregator aggregator, 1179 Table table, 1180 MondrianDef.Expression expression, 1181 Dialect.Datatype datatype) 1182 { 1183 super(name, table, expression, datatype); 1184 this.cubeName = cubeName; 1185 this.aggregator = aggregator; 1186 } 1187 1188 public RolapAggregator getAggregator() { 1189 return aggregator; 1190 } 1191 1192 public boolean equals(Object o) { 1193 if (! (o instanceof RolapStar.Measure)) { 1194 return false; 1195 } 1196 RolapStar.Measure that = (RolapStar.Measure) o; 1197 if (!super.equals(that)) { 1198 return false; 1199 } 1200 // Measure names are only unique within their cube - and remember 1201 // that a given RolapStar can support multiple cubes if they have 1202 // the same fact table. 1203 if (!cubeName.equals(that.cubeName)) { 1204 return false; 1205 } 1206 // Note: both measure have to have the same aggregator 1207 return (that.aggregator == this.aggregator); 1208 } 1209 1210 public int hashCode() { 1211 int h = super.hashCode(); 1212 h = Util.hash(h, aggregator); 1213 return h; 1214 } 1215 1216 public void print(PrintWriter pw, String prefix) { 1217 SqlQuery sqlQuery = getSqlQuery(); 1218 pw.print(prefix); 1219 pw.print(getName()); 1220 pw.print(" ("); 1221 pw.print(getBitPosition()); 1222 pw.print("): "); 1223 pw.print( 1224 aggregator.getExpression( 1225 getExpression() == null 1226 ? null 1227 : generateExprString(sqlQuery))); 1228 } 1229 1230 public String getCubeName() { 1231 return cubeName; 1232 } 1233 } 1234 1235 /** 1236 * Definition of a table in a star schema. 1237 * 1238 * <p>A 'table' is defined by a 1239 * {@link mondrian.olap.MondrianDef.RelationOrJoin} so may, in fact, be a 1240 * view. 1241 * 1242 * <p>Every table in the star schema except the fact table has a parent 1243 * table, and a condition which specifies how it is joined to its parent. 1244 * So the star schema is, in effect, a hierarchy with the fact table at 1245 * its root. 1246 */ 1247 public static class Table { 1248 private final RolapStar star; 1249 private final MondrianDef.Relation relation; 1250 private final List<Column> columnList; 1251 private final Table parent; 1252 private List<Table> children; 1253 private final Condition joinCondition; 1254 private final String alias; 1255 1256 private Table( 1257 RolapStar star, 1258 MondrianDef.Relation relation, 1259 Table parent, 1260 Condition joinCondition) 1261 { 1262 this.star = star; 1263 this.relation = relation; 1264 this.alias = chooseAlias(); 1265 this.parent = parent; 1266 final AliasReplacer aliasReplacer = 1267 new AliasReplacer(relation.getAlias(), this.alias); 1268 this.joinCondition = aliasReplacer.visit(joinCondition); 1269 if (this.joinCondition != null) { 1270 this.joinCondition.table = this; 1271 } 1272 this.columnList = new ArrayList<Column>(); 1273 this.children = Collections.emptyList(); 1274 Util.assertTrue((parent == null) == (joinCondition == null)); 1275 } 1276 1277 /** 1278 * Returns the condition by which a dimension table is connected to its 1279 * {@link #getParentTable() parent}; or null if this is the fact table. 1280 */ 1281 public Condition getJoinCondition() { 1282 return joinCondition; 1283 } 1284 1285 /** 1286 * Returns this table's parent table, or null if this is the fact table 1287 * (which is at the center of the star). 1288 */ 1289 public Table getParentTable() { 1290 return parent; 1291 } 1292 1293 private void addColumn(Column column) { 1294 columnList.add(column); 1295 } 1296 1297 /** 1298 * Adds to a list all columns of this table or a child table 1299 * which are present in a given bitKey. 1300 * 1301 * <p>Note: This method is slow, but that's acceptable because it is 1302 * only used for tracing. It would be more efficient to store an 1303 * array in the {@link RolapStar} mapping column ordinals to columns. 1304 */ 1305 private void collectColumns(BitKey bitKey, List<Column> list) { 1306 for (Column column : getColumns()) { 1307 if (bitKey.get(column.getBitPosition())) { 1308 list.add(column); 1309 } 1310 } 1311 for (Table table : getChildren()) { 1312 table.collectColumns(bitKey, list); 1313 } 1314 } 1315 1316 /** 1317 * Returns an array of all columns in this star with a given name. 1318 */ 1319 public Column[] lookupColumns(String columnName) { 1320 List<Column> l = new ArrayList<Column>(); 1321 for (Column column : getColumns()) { 1322 if (column.getExpression() instanceof MondrianDef.Column) { 1323 MondrianDef.Column columnExpr = 1324 (MondrianDef.Column) column.getExpression(); 1325 if (columnExpr.name.equals(columnName)) { 1326 l.add(column); 1327 } 1328 } else if (column.getExpression() 1329 instanceof MondrianDef.KeyExpression) 1330 { 1331 MondrianDef.KeyExpression columnExpr = 1332 (MondrianDef.KeyExpression) column.getExpression(); 1333 if (columnExpr.toString().equals(columnName)) { 1334 l.add(column); 1335 } 1336 } 1337 } 1338 return l.toArray(new Column[l.size()]); 1339 } 1340 1341 public Column lookupColumn(String columnName) { 1342 for (Column column : getColumns()) { 1343 if (column.getExpression() instanceof MondrianDef.Column) { 1344 MondrianDef.Column columnExpr = 1345 (MondrianDef.Column) column.getExpression(); 1346 if (columnExpr.name.equals(columnName)) { 1347 return column; 1348 } 1349 } else if (column.getExpression() 1350 instanceof MondrianDef.KeyExpression) 1351 { 1352 MondrianDef.KeyExpression columnExpr = 1353 (MondrianDef.KeyExpression) column.getExpression(); 1354 if (columnExpr.toString().equals(columnName)) { 1355 return column; 1356 } 1357 } else if (column.getName().equals(columnName)) { 1358 return column; 1359 } 1360 } 1361 return null; 1362 } 1363 1364 /** 1365 * Given a MondrianDef.Expression return a column with that expression 1366 * or null. 1367 */ 1368 public Column lookupColumnByExpression(MondrianDef.Expression xmlExpr) { 1369 for (Column column : getColumns()) { 1370 if (column instanceof Measure) { 1371 continue; 1372 } 1373 if (column.getExpression().equals(xmlExpr)) { 1374 return column; 1375 } 1376 } 1377 return null; 1378 } 1379 1380 public boolean containsColumn(Column column) { 1381 return getColumns().contains(column); 1382 } 1383 1384 /** 1385 * Look up a {@link Measure} by its name. 1386 * Returns null if not found. 1387 */ 1388 public Measure lookupMeasureByName(String cubeName, String name) { 1389 for (Column column : getColumns()) { 1390 if (column instanceof Measure) { 1391 Measure measure = (Measure) column; 1392 if (measure.getName().equals(name) 1393 && measure.getCubeName().equals(cubeName)) 1394 { 1395 return measure; 1396 } 1397 } 1398 } 1399 return null; 1400 } 1401 1402 RolapStar getStar() { 1403 return star; 1404 } 1405 private SqlQuery getSqlQuery() { 1406 return getStar().getSqlQuery(); 1407 } 1408 public MondrianDef.Relation getRelation() { 1409 return relation; 1410 } 1411 1412 /** Chooses an alias which is unique within the star. */ 1413 private String chooseAlias() { 1414 List<String> aliasList = star.getAliasList(); 1415 for (int i = 0;; ++i) { 1416 String candidateAlias = relation.getAlias(); 1417 if (i > 0) { 1418 candidateAlias += "_" + i; 1419 } 1420 if (!aliasList.contains(candidateAlias)) { 1421 return candidateAlias; 1422 } 1423 } 1424 } 1425 1426 public String getAlias() { 1427 return alias; 1428 } 1429 1430 /** 1431 * Sometimes one need to get to the "real" name when the table has 1432 * been given an alias. 1433 */ 1434 public String getTableName() { 1435 if (relation instanceof MondrianDef.Table) { 1436 MondrianDef.Table t = (MondrianDef.Table) relation; 1437 return t.name; 1438 } else { 1439 return null; 1440 } 1441 } 1442 1443 synchronized void makeMeasure(RolapBaseCubeMeasure measure) { 1444 // Remove assertion to allow cube to be recreated 1445 // assert lookupMeasureByName( 1446 // measure.getCube().getName(), measure.getName()) == null; 1447 RolapStar.Measure starMeasure = new RolapStar.Measure( 1448 measure.getName(), 1449 measure.getCube().getName(), 1450 measure.getAggregator(), 1451 this, 1452 measure.getMondrianDefExpression(), 1453 measure.getDatatype()); 1454 1455 measure.setStarMeasure(starMeasure); // reverse mapping 1456 1457 if (containsColumn(starMeasure)) { 1458 star.decrementColumnCount(); 1459 } else { 1460 addColumn(starMeasure); 1461 } 1462 } 1463 1464 /** 1465 * This is only called by RolapCube. If the RolapLevel has a non-null 1466 * name expression then two columns will be made, otherwise only one. 1467 * Updates the RolapLevel to RolapStar.Column mapping associated with 1468 * this cube. 1469 * 1470 * @param cube Cube 1471 * @param level Level 1472 * @param parentColumn Parent column 1473 */ 1474 synchronized Column makeColumns( 1475 RolapCube cube, 1476 RolapCubeLevel level, 1477 Column parentColumn, 1478 String usagePrefix) 1479 { 1480 Column nameColumn = null; 1481 if (level.getNameExp() != null) { 1482 // make a column for the name expression 1483 nameColumn = makeColumnForLevelExpr( 1484 cube, 1485 level, 1486 level.getName(), 1487 level.getNameExp(), 1488 Dialect.Datatype.String, 1489 null, 1490 null, 1491 null, 1492 null); 1493 } 1494 1495 // select the column's name depending upon whether or not a 1496 // "named" column, above, has been created. 1497 String name = (level.getNameExp() == null) 1498 ? level.getName() 1499 : level.getName() + " (Key)"; 1500 1501 // If the nameColumn is not null, then it is associated with this 1502 // column. 1503 Column column = makeColumnForLevelExpr( 1504 cube, 1505 level, 1506 name, 1507 level.getKeyExp(), 1508 level.getDatatype(), 1509 level.getInternalType(), 1510 nameColumn, 1511 parentColumn, 1512 usagePrefix); 1513 1514 if (column != null) { 1515 level.setStarKeyColumn(column); 1516 } 1517 1518 return column; 1519 } 1520 1521 private Column makeColumnForLevelExpr( 1522 RolapCube cube, 1523 RolapLevel level, 1524 String name, 1525 MondrianDef.Expression xmlExpr, 1526 Dialect.Datatype datatype, 1527 SqlStatement.Type internalType, 1528 Column nameColumn, 1529 Column parentColumn, 1530 String usagePrefix) 1531 { 1532 Table table = this; 1533 if (xmlExpr instanceof MondrianDef.Column) { 1534 final MondrianDef.Column xmlColumn = 1535 (MondrianDef.Column) xmlExpr; 1536 1537 String tableName = xmlColumn.table; 1538 table = findAncestor(tableName); 1539 if (table == null) { 1540 throw Util.newError( 1541 "Level '" + level.getUniqueName() 1542 + "' of cube '" 1543 + this 1544 + "' is invalid: table '" + tableName 1545 + "' is not found in current scope" 1546 + Util.nl 1547 + ", star:" 1548 + Util.nl 1549 + getStar()); 1550 } 1551 RolapStar.AliasReplacer aliasReplacer = 1552 new RolapStar.AliasReplacer(tableName, table.getAlias()); 1553 xmlExpr = aliasReplacer.visit(xmlExpr); 1554 } 1555 // does the column already exist?? 1556 Column c = lookupColumnByExpression(xmlExpr); 1557 1558 RolapStar.Column column; 1559 // Verify Column is not null and not the same as the 1560 // nameColumn created previously (bug 1438285) 1561 if (c != null && !c.equals(nameColumn)) { 1562 // Yes, well just reuse it 1563 // You might wonder why the column need be returned if it 1564 // already exists. Well, it might have been created for one 1565 // cube, but for another cube using the same fact table, it 1566 // still needs to be put into the cube level to column map. 1567 // Trust me, return null and a junit test fails. 1568 column = c; 1569 } else { 1570 // Make a new column and add it 1571 column = new RolapStar.Column( 1572 name, 1573 table, 1574 xmlExpr, 1575 datatype, 1576 internalType, 1577 nameColumn, 1578 parentColumn, 1579 usagePrefix, 1580 level.getApproxRowCount(), 1581 star.nextColumnCount()); 1582 addColumn(column); 1583 } 1584 return column; 1585 } 1586 1587 /** 1588 * Extends this 'leg' of the star by adding <code>relation</code> 1589 * joined by <code>joinCondition</code>. If the same expression is 1590 * already present, does not create it again. Stores the unaliased 1591 * table names to RolapStar.Table mapping associated with the 1592 * input <code>cube</code>. 1593 */ 1594 synchronized Table addJoin( 1595 RolapCube cube, 1596 MondrianDef.RelationOrJoin relationOrJoin, 1597 RolapStar.Condition joinCondition) 1598 { 1599 if (relationOrJoin instanceof MondrianDef.Relation) { 1600 final MondrianDef.Relation relation = 1601 (MondrianDef.Relation) relationOrJoin; 1602 RolapStar.Table starTable = 1603 findChild(relation, joinCondition); 1604 if (starTable == null) { 1605 starTable = new RolapStar.Table( 1606 star, relation, this, joinCondition); 1607 if (this.children.isEmpty()) { 1608 this.children = new ArrayList<Table>(); 1609 } 1610 this.children.add(starTable); 1611 } 1612 return starTable; 1613 } else if (relationOrJoin instanceof MondrianDef.Join) { 1614 MondrianDef.Join join = (MondrianDef.Join) relationOrJoin; 1615 RolapStar.Table leftTable = 1616 addJoin(cube, join.left, joinCondition); 1617 String leftAlias = join.leftAlias; 1618 if (leftAlias == null) { 1619 // REVIEW: is cast to Relation valid? 1620 leftAlias = ((MondrianDef.Relation) join.left).getAlias(); 1621 if (leftAlias == null) { 1622 throw Util.newError( 1623 "missing leftKeyAlias in " + relationOrJoin); 1624 } 1625 } 1626 assert leftTable.findAncestor(leftAlias) == leftTable; 1627 // switch to uniquified alias 1628 leftAlias = leftTable.getAlias(); 1629 1630 String rightAlias = join.rightAlias; 1631 if (rightAlias == null) { 1632 // the right relation of a join may be a join 1633 // if so, we need to use the right relation join's 1634 // left relation's alias. 1635 if (join.right instanceof MondrianDef.Join) { 1636 MondrianDef.Join joinright = 1637 (MondrianDef.Join) join.right; 1638 // REVIEW: is cast to Relation valid? 1639 rightAlias = 1640 ((MondrianDef.Relation) joinright.left) 1641 .getAlias(); 1642 } else { 1643 // REVIEW: is cast to Relation valid? 1644 rightAlias = 1645 ((MondrianDef.Relation) join.right) 1646 .getAlias(); 1647 } 1648 if (rightAlias == null) { 1649 throw Util.newError( 1650 "missing rightKeyAlias in " + relationOrJoin); 1651 } 1652 } 1653 joinCondition = new RolapStar.Condition( 1654 new MondrianDef.Column(leftAlias, join.leftKey), 1655 new MondrianDef.Column(rightAlias, join.rightKey)); 1656 RolapStar.Table rightTable = leftTable.addJoin( 1657 cube, join.right, joinCondition); 1658 return rightTable; 1659 1660 } else { 1661 throw Util.newInternal("bad relation type " + relationOrJoin); 1662 } 1663 } 1664 1665 /** 1666 * Returns a child relation which maps onto a given relation, or null 1667 * if there is none. 1668 */ 1669 public Table findChild( 1670 MondrianDef.Relation relation, 1671 Condition joinCondition) 1672 { 1673 for (Table child : getChildren()) { 1674 if (child.relation.equals(relation)) { 1675 Condition condition = joinCondition; 1676 if (!Util.equalName(relation.getAlias(), child.alias)) { 1677 // Make the two conditions comparable, by replacing 1678 // occurrence of this table's alias with occurrences 1679 // of the child's alias. 1680 AliasReplacer aliasReplacer = new AliasReplacer( 1681 relation.getAlias(), child.alias); 1682 condition = aliasReplacer.visit(joinCondition); 1683 } 1684 if (child.joinCondition.equals(condition)) { 1685 return child; 1686 } 1687 } 1688 } 1689 return null; 1690 } 1691 1692 /** 1693 * Returns a descendant with a given alias, or null if none found. 1694 */ 1695 public Table findDescendant(String seekAlias) { 1696 if (getAlias().equals(seekAlias)) { 1697 return this; 1698 } 1699 for (Table child : getChildren()) { 1700 Table found = child.findDescendant(seekAlias); 1701 if (found != null) { 1702 return found; 1703 } 1704 } 1705 return null; 1706 } 1707 1708 /** 1709 * Returns an ancestor with a given alias, or null if not found. 1710 */ 1711 public Table findAncestor(String tableName) { 1712 for (Table t = this; t != null; t = t.parent) { 1713 if (t.relation.getAlias().equals(tableName)) { 1714 return t; 1715 } 1716 } 1717 return null; 1718 } 1719 1720 public boolean equalsTableName(String tableName) { 1721 if (this.relation instanceof MondrianDef.Table) { 1722 MondrianDef.Table mt = (MondrianDef.Table) this.relation; 1723 if (mt.name.equals(tableName)) { 1724 return true; 1725 } 1726 } 1727 return false; 1728 } 1729 1730 /** 1731 * Adds this table to the FROM clause of a query, and also, if 1732 * <code>joinToParent</code>, any join condition. 1733 * 1734 * @param query Query to add to 1735 * @param failIfExists Pass in false if you might have already added 1736 * the table before and if that happens you want to do nothing. 1737 * @param joinToParent Pass in true if you are constraining a cell 1738 * calculation, false if you are retrieving members. 1739 */ 1740 public void addToFrom( 1741 SqlQuery query, 1742 boolean failIfExists, 1743 boolean joinToParent) 1744 { 1745 query.addFrom(relation, alias, failIfExists); 1746 Util.assertTrue((parent == null) == (joinCondition == null)); 1747 if (joinToParent) { 1748 if (parent != null) { 1749 parent.addToFrom(query, failIfExists, joinToParent); 1750 } 1751 if (joinCondition != null) { 1752 query.addWhere(joinCondition.toString(query)); 1753 } 1754 } 1755 } 1756 1757 /** 1758 * Returns a list of child {@link Table}s. 1759 */ 1760 public List<Table> getChildren() { 1761 return children; 1762 } 1763 1764 /** 1765 * Returns a list of this table's {@link Column}s. 1766 */ 1767 public List<Column> getColumns() { 1768 return columnList; 1769 } 1770 1771 /** 1772 * Finds the child table of the fact table with the given columnName 1773 * used in its left join condition. This is used by the AggTableManager 1774 * while characterizing the fact table columns. 1775 */ 1776 public RolapStar.Table findTableWithLeftJoinCondition( 1777 final String columnName) 1778 { 1779 for (Table child : getChildren()) { 1780 Condition condition = child.joinCondition; 1781 if (condition != null) { 1782 if (condition.left instanceof MondrianDef.Column) { 1783 MondrianDef.Column mcolumn = 1784 (MondrianDef.Column) condition.left; 1785 if (mcolumn.name.equals(columnName)) { 1786 return child; 1787 } 1788 } 1789 } 1790 } 1791 return null; 1792 } 1793 1794 /** 1795 * This is used during aggregate table validation to make sure that the 1796 * mapping from for the aggregate join condition is valid. It returns 1797 * the child table with the matching left join condition. 1798 */ 1799 public RolapStar.Table findTableWithLeftCondition( 1800 final MondrianDef.Expression left) 1801 { 1802 for (Table child : getChildren()) { 1803 Condition condition = child.joinCondition; 1804 if (condition != null) { 1805 if (condition.left instanceof MondrianDef.Column) { 1806 MondrianDef.Column mcolumn = 1807 (MondrianDef.Column) condition.left; 1808 if (mcolumn.equals(left)) { 1809 return child; 1810 } 1811 } 1812 } 1813 } 1814 return null; 1815 } 1816 1817 /** 1818 * Note: I do not think that this is ever true. 1819 */ 1820 public boolean isFunky() { 1821 return (relation == null); 1822 } 1823 1824 public boolean equals(Object obj) { 1825 if (!(obj instanceof Table)) { 1826 return false; 1827 } 1828 Table other = (Table) obj; 1829 return getAlias().equals(other.getAlias()); 1830 } 1831 public int hashCode() { 1832 return getAlias().hashCode(); 1833 } 1834 1835 public String toString() { 1836 StringWriter sw = new StringWriter(256); 1837 PrintWriter pw = new PrintWriter(sw); 1838 print(pw, ""); 1839 pw.flush(); 1840 return sw.toString(); 1841 } 1842 1843 /** 1844 * Prints this table and its children. 1845 */ 1846 public void print(PrintWriter pw, String prefix) { 1847 pw.print(prefix); 1848 pw.println("Table:"); 1849 String subprefix = prefix + " "; 1850 1851 pw.print(subprefix); 1852 pw.print("alias="); 1853 pw.println(getAlias()); 1854 1855 if (this.relation != null) { 1856 pw.print(subprefix); 1857 pw.print("relation="); 1858 pw.println(relation); 1859 } 1860 1861 pw.print(subprefix); 1862 pw.println("Columns:"); 1863 String subsubprefix = subprefix + " "; 1864 1865 for (Column column : getColumns()) { 1866 column.print(pw, subsubprefix); 1867 pw.println(); 1868 } 1869 1870 if (this.joinCondition != null) { 1871 this.joinCondition.print(pw, subprefix); 1872 } 1873 for (Table child : getChildren()) { 1874 child.print(pw, subprefix); 1875 } 1876 } 1877 1878 /** 1879 * Returns whether this table has a column with the given name. 1880 */ 1881 public boolean containsColumn(String columnName) { 1882 if (relation instanceof MondrianDef.Relation) { 1883 return star.containsColumn( 1884 ((MondrianDef.Relation) relation).getAlias(), 1885 columnName); 1886 } else { 1887 // todo: Deal with join. 1888 return false; 1889 } 1890 } 1891 } 1892 1893 public static class Condition { 1894 private static final Logger LOGGER = Logger.getLogger(Condition.class); 1895 1896 private final MondrianDef.Expression left; 1897 private final MondrianDef.Expression right; 1898 // set in Table constructor 1899 Table table; 1900 1901 Condition( 1902 MondrianDef.Expression left, 1903 MondrianDef.Expression right) 1904 { 1905 assert left != null; 1906 assert right != null; 1907 1908 if (!(left instanceof MondrianDef.Column)) { 1909 // TODO: Will this ever print?? if not then left should be 1910 // of type MondrianDef.Column. 1911 LOGGER.debug( 1912 "Condition.left NOT Column: " 1913 + left.getClass().getName()); 1914 } 1915 this.left = left; 1916 this.right = right; 1917 } 1918 public MondrianDef.Expression getLeft() { 1919 return left; 1920 } 1921 public String getLeft(final SqlQuery query) { 1922 return this.left.getExpression(query); 1923 } 1924 public MondrianDef.Expression getRight() { 1925 return right; 1926 } 1927 public String getRight(final SqlQuery query) { 1928 return this.right.getExpression(query); 1929 } 1930 public String toString(SqlQuery query) { 1931 return left.getExpression(query) + " = " 1932 + right.getExpression(query); 1933 } 1934 public int hashCode() { 1935 return left.hashCode() ^ right.hashCode(); 1936 } 1937 1938 public boolean equals(Object obj) { 1939 if (!(obj instanceof Condition)) { 1940 return false; 1941 } 1942 Condition that = (Condition) obj; 1943 return this.left.equals(that.left) 1944 && this.right.equals(that.right); 1945 } 1946 1947 public String toString() { 1948 StringWriter sw = new StringWriter(256); 1949 PrintWriter pw = new PrintWriter(sw); 1950 print(pw, ""); 1951 pw.flush(); 1952 return sw.toString(); 1953 } 1954 1955 /** 1956 * Prints this table and its children. 1957 */ 1958 public void print(PrintWriter pw, String prefix) { 1959 SqlQuery sqlQueuy = table.getSqlQuery(); 1960 pw.print(prefix); 1961 pw.println("Condition:"); 1962 String subprefix = prefix + " "; 1963 1964 pw.print(subprefix); 1965 pw.print("left="); 1966 // print the foreign key bit position if we can figure it out 1967 if (left instanceof MondrianDef.Column) { 1968 MondrianDef.Column c = (MondrianDef.Column) left; 1969 Column col = table.star.getFactTable().lookupColumn(c.name); 1970 if (col != null) { 1971 pw.print(" ("); 1972 pw.print(col.getBitPosition()); 1973 pw.print(") "); 1974 } 1975 } 1976 pw.println(left.getExpression(sqlQueuy)); 1977 1978 pw.print(subprefix); 1979 pw.print("right="); 1980 pw.println(right.getExpression(sqlQueuy)); 1981 } 1982 } 1983 1984 /** 1985 * Creates a copy of an expression, everywhere replacing one alias 1986 * with another. 1987 */ 1988 public static class AliasReplacer { 1989 private final String oldAlias; 1990 private final String newAlias; 1991 1992 public AliasReplacer(String oldAlias, String newAlias) { 1993 this.oldAlias = oldAlias; 1994 this.newAlias = newAlias; 1995 } 1996 1997 private Condition visit(Condition condition) { 1998 if (condition == null) { 1999 return null; 2000 } 2001 if (newAlias.equals(oldAlias)) { 2002 return condition; 2003 } 2004 return new Condition( 2005 visit(condition.left), 2006 visit(condition.right)); 2007 } 2008 2009 public MondrianDef.Expression visit(MondrianDef.Expression expression) { 2010 if (expression == null) { 2011 return null; 2012 } 2013 if (newAlias.equals(oldAlias)) { 2014 return expression; 2015 } 2016 if (expression instanceof MondrianDef.Column) { 2017 MondrianDef.Column column = (MondrianDef.Column) expression; 2018 return new MondrianDef.Column(visit(column.table), column.name); 2019 } else { 2020 throw Util.newInternal("need to implement " + expression); 2021 } 2022 } 2023 2024 private String visit(String table) { 2025 return table.equals(oldAlias) 2026 ? newAlias 2027 : table; 2028 } 2029 } 2030 2031 /** 2032 * Comparator to compare columns based on their name 2033 */ 2034 public static class ColumnComparator implements Comparator<Column> { 2035 2036 public static ColumnComparator instance = new ColumnComparator(); 2037 2038 private ColumnComparator() { 2039 } 2040 2041 public int compare(Column o1, Column o2) { 2042 return o1.getName().compareTo(o2.getName()); 2043 } 2044 } 2045} 2046 2047// End RolapStar.java