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) 2005-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap.aggmatcher; 012 013import mondrian.olap.*; 014import mondrian.olap.MondrianDef.AggLevel; 015import mondrian.recorder.MessageRecorder; 016import mondrian.resource.MondrianResource; 017import mondrian.rolap.*; 018import mondrian.rolap.aggmatcher.JdbcSchema.Table.Column.Usage; 019import mondrian.rolap.aggmatcher.JdbcSchema.UsageType; 020import mondrian.rolap.sql.SqlQuery; 021import mondrian.server.Execution; 022import mondrian.server.Locus; 023import mondrian.spi.Dialect; 024 025import org.apache.log4j.Logger; 026 027import java.io.PrintWriter; 028import java.io.StringWriter; 029import java.sql.ResultSet; 030import java.sql.SQLException; 031import java.util.*; 032 033import javax.sql.DataSource; 034 035/** 036 * Aggregate table version of a RolapStar for a fact table. 037 * 038 * <p>There is the following class structure: 039 * <pre> 040 * AggStar 041 * Table 042 * JoinCondition 043 * Column 044 * Level extends Column 045 * FactTable extends Table 046 * Measure extends Table.Column 047 * DimTable extends Table 048 * </pre> 049 * 050 * <p>Each inner class is non-static meaning that instances have implied 051 * references to the enclosing object. 052 * 053 * @author Richard M. Emberson 054 */ 055public class AggStar { 056 private static final Logger LOGGER = Logger.getLogger(AggStar.class); 057 058 static Logger getLogger() { 059 return LOGGER; 060 } 061 062 private static final MondrianResource mres = MondrianResource.instance(); 063 064 /** 065 * Creates an AggStar and all of its {@link Table}, {@link Table.Column}s, 066 * etc. 067 */ 068 public static AggStar makeAggStar( 069 final RolapStar star, 070 final JdbcSchema.Table dbTable, 071 final MessageRecorder msgRecorder, 072 final int approxRowCount) 073 { 074 AggStar aggStar = new AggStar(star, dbTable, approxRowCount); 075 AggStar.FactTable aggStarFactTable = aggStar.getFactTable(); 076 077 // 1. load fact count 078 for (Iterator<JdbcSchema.Table.Column.Usage> it = 079 dbTable.getColumnUsages(JdbcSchema.UsageType.FACT_COUNT); 080 it.hasNext();) 081 { 082 JdbcSchema.Table.Column.Usage usage = it.next(); 083 aggStarFactTable.loadFactCount(usage); 084 } 085 086 // 2. load measures 087 for (Iterator<JdbcSchema.Table.Column.Usage> it = 088 dbTable.getColumnUsages(JdbcSchema.UsageType.MEASURE); 089 it.hasNext();) 090 { 091 JdbcSchema.Table.Column.Usage usage = it.next(); 092 aggStarFactTable.loadMeasure(usage); 093 } 094 095 // 3. load foreign keys 096 for (Iterator<JdbcSchema.Table.Column.Usage> it = 097 dbTable.getColumnUsages(JdbcSchema.UsageType.FOREIGN_KEY); 098 it.hasNext();) 099 { 100 JdbcSchema.Table.Column.Usage usage = it.next(); 101 aggStarFactTable.loadForeignKey(usage); 102 } 103 104 // 4. load levels 105 for (Iterator<JdbcSchema.Table.Column.Usage> it = 106 dbTable.getColumnUsages(JdbcSchema.UsageType.LEVEL); 107 it.hasNext();) 108 { 109 JdbcSchema.Table.Column.Usage usage = it.next(); 110 aggStarFactTable.loadLevel(usage); 111 } 112 113 // 5. for each distinct-count measure, populate a list of the levels 114 // which it is OK to roll up 115 for (FactTable.Measure measure : aggStarFactTable.measures) { 116 if (measure.aggregator.isDistinct() 117 && measure.argument instanceof MondrianDef.Column) 118 { 119 setLevelBits( 120 measure.rollableLevelBitKey, 121 aggStarFactTable, 122 (MondrianDef.Column) measure.argument, 123 star.getFactTable()); 124 } 125 } 126 127 return aggStar; 128 } 129 130 /** 131 * Sets bits in the bitmap for all levels reachable from a given table. 132 * This allows us to compute which levels can be safely aggregated away 133 * when rolling up a distinct-count measure. 134 * 135 * <p>For example, when rolling up a measure based on 136 * 'COUNT(DISTINCT customer_id)', all levels in the Customers table 137 * and the Regions table reached via the Customers table can be rolled up. 138 * So method sets the bit for all of these levels. 139 * 140 * @param bitKey Bit key of levels which can be rolled up 141 * @param aggTable Fact or dimension table which is the start point for 142 * the navigation 143 * @param column Foreign-key column which constraints which dimension 144 */ 145 private static void setLevelBits( 146 final BitKey bitKey, 147 Table aggTable, 148 MondrianDef.Column column, 149 RolapStar.Table table) 150 { 151 final Set<RolapStar.Column> columns = new HashSet<RolapStar.Column>(); 152 RolapStar.collectColumns(columns, table, column); 153 154 final List<Table.Level> levelList = new ArrayList<Table.Level>(); 155 collectLevels(levelList, aggTable, null); 156 157 for (Table.Level level : levelList) { 158 if (columns.contains(level.starColumn)) { 159 bitKey.set(level.getBitPosition()); 160 } 161 } 162 } 163 164 private static void collectLevels( 165 List<Table.Level> levelList, 166 Table table, 167 MondrianDef.Column joinColumn) 168 { 169 if (joinColumn == null) { 170 levelList.addAll(table.levels); 171 } 172 for (Table dimTable : table.children) { 173 if (joinColumn != null 174 && !dimTable.getJoinCondition().left.equals(joinColumn)) 175 { 176 continue; 177 } 178 collectLevels(levelList, dimTable, null); 179 } 180 } 181 182 private final RolapStar star; 183 private final AggStar.FactTable aggTable; 184 185 /** 186 * This BitKey is for all of the columns in the AggStar (levels and 187 * measures). 188 */ 189 private final BitKey bitKey; 190 191 /** 192 * BitKey of the levels (levels and foreign keys) of this AggStar. 193 */ 194 private final BitKey levelBitKey; 195 196 /** 197 * BitKey of the measures of this AggStar. 198 */ 199 private final BitKey measureBitKey; 200 201 /** 202 * BitKey of the foreign keys of this AggStar. 203 */ 204 private final BitKey foreignKeyBitKey; 205 206 /** 207 * BitKey of those measures of this AggStar that are distinct count 208 * aggregates. 209 */ 210 private final BitKey distinctMeasureBitKey; 211 private final AggStar.Table.Column[] columns; 212 /** 213 * A map of bit positions to columns which need to 214 * be joined and are not collapsed. If the aggregate table 215 * includes an {@link AggLevel} element which is not 216 * collapsed, it will appear in that list. We use this 217 * list later on to create the join paths in AggQuerySpec. 218 */ 219 private final Map<Integer, AggStar.Table.Column> levelColumnsToJoin; 220 221 /** 222 * An approximate number of rows present in this aggregate table. 223 */ 224 private final int approxRowCount; 225 226 AggStar( 227 final RolapStar star, 228 final JdbcSchema.Table aggTable, 229 final int approxRowCount) 230 { 231 this.star = star; 232 this.approxRowCount = approxRowCount; 233 this.bitKey = BitKey.Factory.makeBitKey(star.getColumnCount()); 234 this.levelBitKey = bitKey.emptyCopy(); 235 this.measureBitKey = bitKey.emptyCopy(); 236 this.foreignKeyBitKey = bitKey.emptyCopy(); 237 this.distinctMeasureBitKey = bitKey.emptyCopy(); 238 this.aggTable = new AggStar.FactTable(aggTable); 239 this.columns = new AggStar.Table.Column[star.getColumnCount()]; 240 this.levelColumnsToJoin = new HashMap<Integer, AggStar.Table.Column>(); 241 } 242 243 /** 244 * Get the fact table. 245 * 246 * @return the fact table 247 */ 248 public AggStar.FactTable getFactTable() { 249 return aggTable; 250 } 251 252 /** 253 * Find a table by name (alias) that is a descendant of the base 254 * fact table. 255 * 256 * @param name the table to find 257 * @return the table or null 258 */ 259 public Table findTable(String name) { 260 AggStar.FactTable table = getFactTable(); 261 return table.findDescendant(name); 262 } 263 264 265 /** 266 * Returns a measure of the IO cost of querying this table. It can be 267 * either the row count or the row count times the size of a row. 268 * If the property {@link MondrianProperties#ChooseAggregateByVolume} 269 * is true, then volume is returned, otherwise row count. 270 */ 271 public int getSize() { 272 return MondrianProperties.instance().ChooseAggregateByVolume.get() 273 ? getFactTable().getVolume() 274 : getFactTable().getNumberOfRows(); 275 } 276 277 void setForeignKey(int index) { 278 this.foreignKeyBitKey.set(index); 279 } 280 public BitKey getForeignKeyBitKey() { 281 return this.foreignKeyBitKey; 282 } 283 284 /** 285 * Is this AggStar's BitKey a super set (proper or not) of the BitKey 286 * parameter. 287 * 288 * @return true if it is a super set 289 */ 290 public boolean superSetMatch(final BitKey bitKey) { 291 return getBitKey().isSuperSetOf(bitKey); 292 } 293 294 /** 295 * Return true if this AggStar's level BitKey equals the 296 * <code>levelBitKey</code> parameter 297 * and if this AggStar's measure BitKey is a super set 298 * (proper or not) of the <code>measureBitKey</code> parameter. 299 */ 300 public boolean select( 301 final BitKey levelBitKey, 302 final BitKey coreLevelBitKey, 303 final BitKey measureBitKey) 304 { 305 if (!getMeasureBitKey().isSuperSetOf(measureBitKey)) { 306 return false; 307 } 308 if (getLevelBitKey().equals(levelBitKey)) { 309 return true; 310 } else if (getLevelBitKey().isSuperSetOf(levelBitKey) 311 && getLevelBitKey().andNot(coreLevelBitKey).equals( 312 levelBitKey.andNot(coreLevelBitKey))) 313 { 314 // It's OK to roll up levels which are orthogonal to the distinct 315 // measure. 316 return true; 317 } else { 318 return false; 319 } 320 } 321 322 /** 323 * Get this AggStar's RolapStar. 324 */ 325 public RolapStar getStar() { 326 return star; 327 } 328 329 /** 330 * Return true if AggStar has measures 331 */ 332 public boolean hasMeasures() { 333 return getFactTable().hasMeasures(); 334 } 335 336 /** 337 * Return true if AggStar has levels 338 */ 339 public boolean hasLevels() { 340 return getFactTable().hasLevels(); 341 } 342 343 /** 344 * Returns whether this AggStar has foreign keys. 345 */ 346 public boolean hasForeignKeys() { 347 return getFactTable().hasChildren(); 348 } 349 350 /** 351 * Returns the BitKey. 352 */ 353 public BitKey getBitKey() { 354 return bitKey; 355 } 356 357 /** 358 * Get the foreign-key/level BitKey. 359 */ 360 public BitKey getLevelBitKey() { 361 return levelBitKey; 362 } 363 364 /** 365 * Returns a BitKey of all measures. 366 */ 367 public BitKey getMeasureBitKey() { 368 return measureBitKey; 369 } 370 371 /** 372 * Returns a BitKey containing only distinct measures. 373 */ 374 public BitKey getDistinctMeasureBitKey() { 375 return distinctMeasureBitKey; 376 } 377 378 /** 379 * Get an SqlQuery instance. 380 */ 381 private SqlQuery getSqlQuery() { 382 return getStar().getSqlQuery(); 383 } 384 385 /** 386 * Get the Measure at the given bit position or return null. 387 * Note that there is no check that the bit position is within the range of 388 * the array of columns. 389 * Nor is there a check that the column type at that position is a Measure. 390 * 391 * @return A Measure or null. 392 */ 393 public AggStar.FactTable.Measure lookupMeasure(final int bitPos) { 394 AggStar.Table.Column column = lookupColumn(bitPos); 395 return (column instanceof AggStar.FactTable.Measure) 396 ? (AggStar.FactTable.Measure) column 397 : null; 398 } 399 /** 400 * Get the Level at the given bit position or return null. 401 * Note that there is no check that the bit position is within the range of 402 * the array of columns. 403 * Nor is there a check that the column type at that position is a Level. 404 * 405 * @return A Level or null. 406 */ 407 public AggStar.Table.Level lookupLevel(final int bitPos) { 408 AggStar.Table.Column column = lookupColumn(bitPos); 409 return (column instanceof AggStar.FactTable.Level) 410 ? (AggStar.FactTable.Level) column 411 : null; 412 } 413 414 /** 415 * Get the Column at the bit position. 416 * Note that there is no check that the bit position is within the range of 417 * the array of columns. 418 */ 419 public AggStar.Table.Column lookupColumn(final int bitPos) { 420 if (columns[bitPos] != null) { 421 return columns[bitPos]; 422 } 423 return levelColumnsToJoin.get(bitPos); 424 } 425 426 /** 427 * Returns true if every level column in the AggStar is collapsed. 428 */ 429 public boolean isFullyCollapsed() { 430 BitSet bitSet = this.getLevelBitKey().toBitSet(); 431 for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) 432 { 433 if (this.lookupLevel(i) == null 434 || !(this.lookupLevel(i)).isCollapsed()) 435 { 436 return false; 437 } 438 } 439 return true; 440 } 441 442 /** 443 * This is called by within the Column constructor. 444 */ 445 private void addColumn(final AggStar.Table.Column column) { 446 columns[column.getBitPosition()] = column; 447 } 448 449 private static final Logger JOIN_CONDITION_LOGGER = 450 Logger.getLogger(AggStar.Table.JoinCondition.class); 451 452 /** 453 * Base Table class for the FactTable and DimTable classes. 454 * This class parallels the RolapStar.Table class. 455 */ 456 public abstract class Table { 457 458 /** 459 * The query join condition between a base table and this table (the 460 * table that owns the join condition). 461 */ 462 public class JoinCondition { 463 // I think this is always a MondrianDef.Column 464 private final MondrianDef.Expression left; 465 private final MondrianDef.Expression right; 466 467 private JoinCondition( 468 final MondrianDef.Expression left, 469 final MondrianDef.Expression right) 470 { 471 if (!(left instanceof MondrianDef.Column)) { 472 JOIN_CONDITION_LOGGER.debug( 473 "JoinCondition.left NOT Column: " 474 + left.getClass().getName()); 475 } 476 this.left = left; 477 this.right = right; 478 } 479 480 /** 481 * Get the enclosing AggStar.Table. 482 */ 483 public Table getTable() { 484 return AggStar.Table.this; 485 } 486 487 /** 488 * Return the left join expression. 489 */ 490 public MondrianDef.Expression getLeft() { 491 return this.left; 492 } 493 494 /** 495 * Return the left join expression as string. 496 */ 497 public String getLeft(final SqlQuery query) { 498 return this.left.getExpression(query); 499 } 500 501 /** 502 * Return the right join expression. 503 */ 504 public MondrianDef.Expression getRight() { 505 return this.right; 506 } 507 508 /** 509 * This is used to create part of a SQL where clause. 510 */ 511 String toString(final SqlQuery query) { 512 StringBuilder buf = new StringBuilder(64); 513 buf.append(left.getExpression(query)); 514 buf.append(" = "); 515 buf.append(right.getExpression(query)); 516 return buf.toString(); 517 } 518 public String toString() { 519 StringWriter sw = new StringWriter(128); 520 PrintWriter pw = new PrintWriter(sw); 521 print(pw, ""); 522 pw.flush(); 523 return sw.toString(); 524 } 525 526 /** 527 * Prints this table and its children. 528 */ 529 public void print(final PrintWriter pw, final String prefix) { 530 SqlQuery sqlQueuy = getTable().getSqlQuery(); 531 pw.print(prefix); 532 pw.println("JoinCondition:"); 533 String subprefix = prefix + " "; 534 535 pw.print(subprefix); 536 pw.print("left="); 537 if (left instanceof MondrianDef.Column) { 538 MondrianDef.Column c = (MondrianDef.Column) left; 539 mondrian.rolap.RolapStar.Column col = 540 getTable().getAggStar().getStar().getFactTable() 541 .lookupColumn(c.name); 542 if (col != null) { 543 pw.print(" ("); 544 pw.print(col.getBitPosition()); 545 pw.print(") "); 546 } 547 } 548 pw.println(left.getExpression(sqlQueuy)); 549 550 pw.print(subprefix); 551 pw.print("right="); 552 pw.println(right.getExpression(sqlQueuy)); 553 } 554 } 555 556 557 /** 558 * Base class for Level and Measure classes 559 */ 560 public class Column { 561 562 private final String name; 563 private final MondrianDef.Expression expression; 564 private final Dialect.Datatype datatype; 565 /** 566 * This is only used in RolapAggregationManager and adds 567 * non-constraining columns making the drill-through queries 568 * easier for humans to understand. 569 */ 570 private final Column nameColumn; 571 572 /** this has a unique value per star */ 573 private final int bitPosition; 574 575 protected Column( 576 final String name, 577 final MondrianDef.Expression expression, 578 final Dialect.Datatype datatype, 579 final int bitPosition) 580 { 581 this.name = name; 582 this.expression = expression; 583 this.datatype = datatype; 584 this.bitPosition = bitPosition; 585 586 this.nameColumn = null; 587 588 // do not count the fact_count column 589 if (bitPosition >= 0) { 590 AggStar.this.bitKey.set(bitPosition); 591 AggStar.this.addColumn(this); 592 } 593 } 594 595 /** 596 * Get the name of the column (this is the name in the database). 597 */ 598 public String getName() { 599 return name; 600 } 601 602 /** 603 * Get the enclosing AggStar.Table. 604 */ 605 public AggStar.Table getTable() { 606 return AggStar.Table.this; 607 } 608 609 /** 610 * Get the bit possition associted with this column. This has the 611 * same value as this column's RolapStar.Column. 612 */ 613 public int getBitPosition() { 614 return bitPosition; 615 } 616 617 /** 618 * Returns the datatype of this column. 619 */ 620 public Dialect.Datatype getDatatype() { 621 return datatype; 622 } 623 624 public SqlQuery getSqlQuery() { 625 return getTable().getAggStar().getSqlQuery(); 626 } 627 628 public MondrianDef.Expression getExpression() { 629 return expression; 630 } 631 632 /** 633 * Generates a SQL expression, which typically this looks like 634 * this: <code><i>tableName</i>.<i>columnName</i></code>. 635 */ 636 public String generateExprString(final SqlQuery query) { 637 String usagePrefix = getUsagePrefix(); 638 String exprString; 639 640 if (usagePrefix == null) { 641 exprString = getExpression().getExpression(query); 642 } else { 643 MondrianDef.Expression expression = getExpression(); 644 assert expression instanceof MondrianDef.Column; 645 MondrianDef.Column columnExpr = 646 (MondrianDef.Column)expression; 647 String prefixedName = usagePrefix 648 + columnExpr.getColumnName(); 649 String tableName = columnExpr.getTableAlias(); 650 exprString = query.getDialect().quoteIdentifier( 651 tableName, prefixedName); 652 } 653 return exprString; 654 } 655 656 private String getUsagePrefix() { 657 String usagePrefix = null; 658 if (this.getBitPosition() != -1) { 659 usagePrefix = getStar().getColumn(this.getBitPosition()) 660 .getUsagePrefix(); 661 } 662 return usagePrefix; 663 } 664 665 666 public String toString() { 667 StringWriter sw = new StringWriter(256); 668 PrintWriter pw = new PrintWriter(sw); 669 print(pw, ""); 670 pw.flush(); 671 return sw.toString(); 672 } 673 public void print(final PrintWriter pw, final String prefix) { 674 SqlQuery sqlQuery = getSqlQuery(); 675 pw.print(prefix); 676 pw.print(getName()); 677 pw.print(" ("); 678 pw.print(getBitPosition()); 679 pw.print("): "); 680 pw.print(generateExprString(sqlQuery)); 681 } 682 683 public SqlStatement.Type getInternalType() { 684 return null; 685 } 686 } 687 688 /** 689 * This class is used for holding foreign key columns. 690 * Both DimTables and FactTables can have Level columns. 691 */ 692 final class ForeignKey extends Column { 693 694 private ForeignKey( 695 final String name, 696 final MondrianDef.Expression expression, 697 final Dialect.Datatype datatype, 698 final int bitPosition) 699 { 700 super(name, expression, datatype, bitPosition); 701 AggStar.this.levelBitKey.set(bitPosition); 702 } 703 } 704 705 /** 706 * This class is used for holding dimension level information. 707 * Both DimTables and FactTables can have Level columns. 708 */ 709 final class Level extends Column { 710 private final RolapStar.Column starColumn; 711 private final boolean collapsed; 712 713 private Level( 714 final String name, 715 final MondrianDef.Expression expression, 716 final int bitPosition, 717 RolapStar.Column starColumn, 718 boolean collapsed) 719 { 720 super(name, expression, starColumn.getDatatype(), bitPosition); 721 this.starColumn = starColumn; 722 this.collapsed = collapsed; 723 AggStar.this.levelBitKey.set(bitPosition); 724 } 725 726 @Override 727 public SqlStatement.Type getInternalType() { 728 return starColumn.getInternalType(); 729 } 730 731 public boolean isCollapsed() { 732 return collapsed; 733 } 734 } 735 736 /** The name of the table in the database. */ 737 private final String name; 738 private final MondrianDef.Relation relation; 739 protected final List<Level> levels = new ArrayList<Level>(); 740 protected List<DimTable> children; 741 742 Table(final String name, final MondrianDef.Relation relation) { 743 this.name = name; 744 this.relation = relation; 745 this.children = Collections.emptyList(); 746 } 747 748 /** 749 * Return the name of the table in the database. 750 */ 751 public String getName() { 752 return name; 753 } 754 755 /** 756 * Return true if this table has a parent table (FactTable instances 757 * do not have parent tables, all other do). 758 */ 759 public abstract boolean hasParent(); 760 761 /** 762 * Get the parent table (returns null if this table is a FactTable). 763 */ 764 public abstract Table getParent(); 765 766 /** 767 * Return true if this table has a join condition (only DimTables have 768 * join conditions, FactTable instances do not). 769 */ 770 public abstract boolean hasJoinCondition(); 771 public abstract Table.JoinCondition getJoinCondition(); 772 773 public MondrianDef.Relation getRelation() { 774 return relation; 775 } 776 777 /** 778 * Get this table's enclosing AggStar. 779 */ 780 protected AggStar getAggStar() { 781 return AggStar.this; 782 } 783 784 /** 785 * Get a SqlQuery object. 786 */ 787 protected SqlQuery getSqlQuery() { 788 return getAggStar().getSqlQuery(); 789 } 790 791 /** 792 * Add a Level column. 793 */ 794 protected void addLevel(final AggStar.Table.Level level) { 795 this.levels.add(level); 796 } 797 798 /** 799 * Returns all level columns. 800 */ 801 public List<Level> getLevels() { 802 return levels; 803 } 804 805 /** 806 * Return true if table has levels. 807 */ 808 public boolean hasLevels() { 809 return ! levels.isEmpty(); 810 } 811 812 /** 813 * Add a child DimTable table. 814 */ 815 protected void addTable(final DimTable child) { 816 if (children == Collections.EMPTY_LIST) { 817 children = new ArrayList<DimTable>(); 818 } 819 children.add(child); 820 } 821 822 /** 823 * Returns a list of child {@link Table} objects. 824 */ 825 public List<DimTable> getChildTables() { 826 return children; 827 } 828 829 /** 830 * Find descendant of fact table with given name or return null. 831 * 832 * @param name the child table name (alias). 833 * @return the child table or null. 834 */ 835 public Table findDescendant(String name) { 836 if (getName().equals(name)) { 837 return this; 838 } 839 840 for (Table child : getChildTables()) { 841 Table found = child.findDescendant(name); 842 if (found != null) { 843 return found; 844 } 845 } 846 return null; 847 } 848 849 /** 850 * Return true if this table has one or more child tables. 851 */ 852 public boolean hasChildren() { 853 return ! children.isEmpty(); 854 } 855 856 /** 857 * Converts a {@link mondrian.rolap.RolapStar.Table} into a 858 * {@link AggStar.DimTable} as well as converting all columns and 859 * child tables. If the rightJoinConditionColumnName parameter 860 * is null, then the table's namd and the rTable parameter's 861 * condition left condition's column name are used to form the 862 * join condition's left expression. 863 */ 864 protected AggStar.DimTable convertTable( 865 final RolapStar.Table rTable, 866 final Usage usage) 867 { 868 String tableName = rTable.getAlias(); 869 MondrianDef.Relation relation = rTable.getRelation(); 870 RolapStar.Condition rjoinCondition = rTable.getJoinCondition(); 871 MondrianDef.Expression rleft = rjoinCondition.getLeft(); 872 final MondrianDef.Expression rright; 873 if (usage == null 874 || usage.getUsageType() != UsageType.LEVEL) 875 { 876 rright = rjoinCondition.getRight(); 877 } else { 878 rright = usage.level.getKeyExp(); 879 } 880 881 MondrianDef.Column left = null; 882 if (usage != null 883 && usage.rightJoinConditionColumnName != null) 884 { 885 left = new MondrianDef.Column( 886 getName(), 887 usage.rightJoinConditionColumnName); 888 } else { 889 if (rleft instanceof MondrianDef.Column) { 890 MondrianDef.Column lcolumn = (MondrianDef.Column) rleft; 891 left = new MondrianDef.Column(getName(), lcolumn.name); 892 } else { 893 throw Util.newInternal("not implemented: rleft=" + rleft); 894/* 895 // RME TODO can we catch this during validation 896 String msg = mres.BadRolapStarLeftJoinCondition.str( 897 "AggStar.Table", 898 rleft.getClass().getName(), left.toString()); 899 getLogger().warn(msg); 900*/ 901 } 902 } 903 // Explicitly set which columns are foreign keys in the 904 // AggStar. This lets us later determine if a measure is 905 // based upon a foreign key (see AggregationManager findAgg 906 // method). 907 mondrian.rolap.RolapStar.Column col = 908 getAggStar().getStar().getFactTable().lookupColumn(left.name); 909 if (col != null) { 910 getAggStar().setForeignKey(col.getBitPosition()); 911 } 912 JoinCondition joinCondition = new JoinCondition(left, rright); 913 DimTable dimTable = 914 new DimTable(this, tableName, relation, joinCondition); 915 916 if (usage == null 917 || usage.getUsageType() != UsageType.LEVEL 918 || (usage.getUsageType() == UsageType.LEVEL 919 && usage.collapsed)) 920 { 921 // Only set the bits for all levels if we are not 922 // dealing with a non-collapsed AggLevel because we will 923 // set the bits manually in AggStar.FactTable.loadLevel. 924 dimTable.convertColumns(rTable); 925 } 926 927 dimTable.convertChildren(rTable); 928 929 return dimTable; 930 } 931 932 /** 933 * Convert a RolapStar.Table table's columns into 934 * AggStar.Table.Level columns. 935 */ 936 protected void convertColumns(final RolapStar.Table rTable) { 937 // add level columns 938 for (RolapStar.Column column : rTable.getColumns()) { 939 String name = column.getName(); 940 MondrianDef.Expression expression = column.getExpression(); 941 int bitPosition = column.getBitPosition(); 942 943 Level level = new Level( 944 name, 945 expression, 946 bitPosition, 947 column, 948 false); 949 addLevel(level); 950 } 951 } 952 953 /** 954 * Convert the child tables of a RolapStar.Table into 955 * child AggStar.DimTable tables. 956 */ 957 protected void convertChildren(final RolapStar.Table rTable) { 958 // add children tables 959 for (RolapStar.Table rTableChild : rTable.getChildren()) { 960 DimTable dimChild = convertTable(rTableChild, null); 961 962 addTable(dimChild); 963 } 964 } 965 966 /** 967 * This is a copy of the code found in RolapStar used to generate an SQL 968 * query. 969 */ 970 public void addToFrom( 971 final SqlQuery query, 972 final boolean failIfExists, 973 final boolean joinToParent) 974 { 975 query.addFrom(relation, name, failIfExists); 976 if (joinToParent) { 977 if (hasParent()) { 978 getParent().addToFrom(query, failIfExists, joinToParent); 979 } 980 if (hasJoinCondition()) { 981 query.addWhere(getJoinCondition().toString(query)); 982 } 983 } 984 } 985 986 public String toString() { 987 StringWriter sw = new StringWriter(256); 988 PrintWriter pw = new PrintWriter(sw); 989 print(pw, ""); 990 pw.flush(); 991 return sw.toString(); 992 } 993 public abstract void print(final PrintWriter pw, final String prefix); 994 } 995 996 /** 997 * This is an aggregate fact table. 998 */ 999 public class FactTable extends Table { 1000 1001 /** 1002 * This is a Column that is a Measure (contains an aggregator). 1003 */ 1004 public class Measure extends Table.Column { 1005 private final RolapAggregator aggregator; 1006 /** 1007 * The fact table column which is being aggregated. 1008 */ 1009 private final MondrianDef.Expression argument; 1010 /** 1011 * For distinct-count measures, contains a bitKey of levels which 1012 * it is OK to roll up. For regular measures, this is empty, since 1013 * all levels can be rolled up. 1014 */ 1015 private final BitKey rollableLevelBitKey; 1016 1017 private Measure( 1018 final String name, 1019 final MondrianDef.Expression expression, 1020 final Dialect.Datatype datatype, 1021 final int bitPosition, 1022 final RolapAggregator aggregator, 1023 final MondrianDef.Expression argument) 1024 { 1025 super(name, expression, datatype, bitPosition); 1026 this.aggregator = aggregator; 1027 this.argument = argument; 1028 assert (argument != null) == aggregator.isDistinct(); 1029 this.rollableLevelBitKey = 1030 BitKey.Factory.makeBitKey(star.getColumnCount()); 1031 1032 AggStar.this.measureBitKey.set(bitPosition); 1033 } 1034 1035 public boolean isDistinct() { 1036 return aggregator.isDistinct(); 1037 } 1038 1039 /** 1040 * Get this Measure's RolapAggregator. 1041 */ 1042 public RolapAggregator getAggregator() { 1043 return aggregator; 1044 } 1045 1046 /** 1047 * Returns a <code>BitKey</code> of the levels which can be 1048 * safely rolled up. (For distinct-count measures, most can't.) 1049 */ 1050 public BitKey getRollableLevelBitKey() { 1051 return rollableLevelBitKey; 1052 } 1053 1054 public String generateRollupString(SqlQuery query) { 1055 String expr = generateExprString(query); 1056 Aggregator rollup = null; 1057 1058 BitKey fkbk = AggStar.this.getForeignKeyBitKey(); 1059 // When rolling up and the aggregator is distinct and 1060 // the measure is based upon a foreign key, then 1061 // one must use "count" rather than "sum" 1062 if (fkbk.get(getBitPosition())) { 1063 rollup = (getAggregator().isDistinct()) 1064 ? getAggregator().getNonDistinctAggregator() 1065 : getAggregator().getRollup(); 1066 } else { 1067 rollup = getAggregator().getRollup(); 1068 } 1069 1070 String s = ((RolapAggregator) rollup).getExpression(expr); 1071 return s; 1072 } 1073 public void print(final PrintWriter pw, final String prefix) { 1074 SqlQuery sqlQuery = getSqlQuery(); 1075 pw.print(prefix); 1076 pw.print(getName()); 1077 pw.print(" ("); 1078 pw.print(getBitPosition()); 1079 pw.print("): "); 1080 pw.print(generateRollupString(sqlQuery)); 1081 } 1082 } 1083 1084 private Column factCountColumn; 1085 private final List<Measure> measures; 1086 private final int totalColumnSize; 1087 private int numberOfRows; 1088 1089 FactTable(final JdbcSchema.Table aggTable) { 1090 this( 1091 aggTable.getName(), 1092 aggTable.table, 1093 aggTable.getTotalColumnSize(), 1094 aggTable.getNumberOfRows()); 1095 } 1096 1097 FactTable( 1098 final String name, 1099 final MondrianDef.Relation relation, 1100 final int totalColumnSize, 1101 final int numberOfRows) 1102 { 1103 super(name, relation); 1104 this.totalColumnSize = totalColumnSize; 1105 this.measures = new ArrayList<Measure>(); 1106 this.numberOfRows = numberOfRows; 1107 } 1108 1109 public Table getParent() { 1110 return null; 1111 } 1112 1113 public boolean hasParent() { 1114 return false; 1115 } 1116 1117 public boolean hasJoinCondition() { 1118 return false; 1119 } 1120 1121 public Table.JoinCondition getJoinCondition() { 1122 return null; 1123 } 1124 1125 /** 1126 * Get the volume of the table (now of rows * size of a row). 1127 */ 1128 public int getVolume() { 1129 return getTotalColumnSize() * getNumberOfRows(); 1130 } 1131 1132 /** 1133 * Get the total size of all columns in a row. 1134 */ 1135 public int getTotalColumnSize() { 1136 return totalColumnSize; 1137 } 1138 1139 /** 1140 * Get the number of rows in this aggregate table. 1141 */ 1142 public int getNumberOfRows() { 1143 if (numberOfRows < 0) { 1144 numberOfRows = 1145 star.getStatisticsCache().getRelationCardinality( 1146 getRelation(), getName(), approxRowCount); 1147 makeNumberOfRows(); 1148 } 1149 return numberOfRows; 1150 } 1151 1152 /** 1153 * This is for testing ONLY. 1154 */ 1155 void setNumberOfRows(int numberOfRows) { 1156 this.numberOfRows = numberOfRows; 1157 } 1158 1159 /** 1160 * Returns a list of all measures. 1161 */ 1162 public List<Measure> getMeasures() { 1163 return measures; 1164 } 1165 1166 /** 1167 * Return true it table has measures 1168 */ 1169 public boolean hasMeasures() { 1170 return ! measures.isEmpty(); 1171 } 1172 1173 /** 1174 * Returns a list of the columns in this table. 1175 */ 1176 public List<Column> getColumns() { 1177 List<Column> list = new ArrayList<Column>(); 1178 list.addAll(measures); 1179 list.addAll(levels); 1180 for (DimTable dimTable : getChildTables()) { 1181 dimTable.addColumnsToList(list); 1182 } 1183 return list; 1184 } 1185 1186 /** 1187 * For a foreign key usage create a child DimTable table. 1188 */ 1189 private void loadForeignKey(final JdbcSchema.Table.Column.Usage usage) { 1190 if (usage.rTable != null) { 1191 DimTable child = convertTable( 1192 usage.rTable, 1193 usage); 1194 addTable(child); 1195 } else { 1196 // it a column thats not a measure or foreign key - it must be 1197 // a non-shared dimension 1198 // See: AggTableManager.java 1199 JdbcSchema.Table.Column column = usage.getColumn(); 1200 String name = column.getName(); 1201 String symbolicName = usage.getSymbolicName(); 1202 if (symbolicName == null) { 1203 symbolicName = name; 1204 } 1205 1206 MondrianDef.Expression expression = 1207 new MondrianDef.Column(getName(), name); 1208 Dialect.Datatype datatype = column.getDatatype(); 1209 RolapStar.Column rColumn = usage.rColumn; 1210 if (rColumn == null) { 1211 getLogger().warn( 1212 "loadForeignKey: for column " 1213 + name 1214 + ", rColumn == null"); 1215 } else { 1216 int bitPosition = rColumn.getBitPosition(); 1217 ForeignKey c = 1218 new ForeignKey( 1219 symbolicName, expression, datatype, bitPosition); 1220 getAggStar().setForeignKey(c.getBitPosition()); 1221 } 1222 } 1223 } 1224 1225 /** 1226 * Given a usage of type measure, create a Measure column. 1227 */ 1228 private void loadMeasure(final JdbcSchema.Table.Column.Usage usage) { 1229 final JdbcSchema.Table.Column column = usage.getColumn(); 1230 String name = column.getName(); 1231 String symbolicName = usage.getSymbolicName(); 1232 if (symbolicName == null) { 1233 symbolicName = name; 1234 } 1235 Dialect.Datatype datatype = column.getDatatype(); 1236 RolapAggregator aggregator = usage.getAggregator(); 1237 1238 MondrianDef.Expression expression; 1239 if (column.hasUsage(JdbcSchema.UsageType.FOREIGN_KEY) 1240 && !aggregator.isDistinct()) 1241 { 1242 expression = factCountColumn.getExpression(); 1243 } else { 1244 expression = new MondrianDef.Column(getName(), name); 1245 } 1246 1247 MondrianDef.Expression argument; 1248 if (aggregator.isDistinct()) { 1249 argument = usage.rMeasure.getExpression(); 1250 } else { 1251 argument = null; 1252 } 1253 1254 int bitPosition = usage.rMeasure.getBitPosition(); 1255 1256 Measure aggMeasure = 1257 new Measure( 1258 symbolicName, 1259 expression, 1260 datatype, 1261 bitPosition, 1262 aggregator, 1263 argument); 1264 1265 measures.add(aggMeasure); 1266 1267 if (aggMeasure.aggregator.isDistinct()) { 1268 distinctMeasureBitKey.set(bitPosition); 1269 } 1270 } 1271 1272 /** 1273 * Create a fact_count column for a usage of type fact count. 1274 */ 1275 private void loadFactCount(final JdbcSchema.Table.Column.Usage usage) { 1276 String name = usage.getColumn().getName(); 1277 String symbolicName = usage.getSymbolicName(); 1278 if (symbolicName == null) { 1279 symbolicName = name; 1280 } 1281 1282 MondrianDef.Expression expression = 1283 new MondrianDef.Column(getName(), name); 1284 Dialect.Datatype datatype = usage.getColumn().getDatatype(); 1285 int bitPosition = -1; 1286 1287 Column aggColumn = 1288 new Column( 1289 symbolicName, 1290 expression, 1291 datatype, 1292 bitPosition); 1293 1294 factCountColumn = aggColumn; 1295 } 1296 1297 /** 1298 * Given a usage of type level, create a Level column. 1299 */ 1300 private void loadLevel(final JdbcSchema.Table.Column.Usage usage) { 1301 String name = usage.getSymbolicName(); 1302 MondrianDef.Expression expression = 1303 new MondrianDef.Column(getName(), usage.levelColumnName); 1304 int bitPosition = usage.rColumn.getBitPosition(); 1305 Level level = 1306 new Level( 1307 name, 1308 expression, 1309 bitPosition, 1310 usage.rColumn, 1311 usage.collapsed); 1312 addLevel(level); 1313 1314 // If we are dealing with a non-collapsed level, we have to 1315 // modify the bit key of the AggStar and create a column 1316 // object for each parent level so that the AggQuerySpec 1317 // can correctly link up to the other tables. 1318 if (!usage.collapsed) { 1319 // We must also update the bit key with 1320 // the parent levels of any non-collapsed level. 1321 RolapLevel parentLevel = 1322 (RolapLevel)usage.level.getParentLevel(); 1323 while (parentLevel != null && !parentLevel.isAll()) { 1324 // Find the bit for this AggStar's bit key for each parent 1325 // level. There is no need to modify the AggStar's bit key 1326 // directly here, because the constructor of Column 1327 // will do that for us later on. 1328 final BitKey bk = AggStar.this.star.getBitKey( 1329 new String[] { 1330 parentLevel.getKeyExp().getTableAlias()}, 1331 new String[] { 1332 ((MondrianDef.Column)parentLevel.getKeyExp()) 1333 .getColumnName()}); 1334 final int bitPos = bk.nextSetBit(0); 1335 if (bitPos == -1) { 1336 throw new MondrianException( 1337 "Failed to match non-collapsed aggregate level with a column from the RolapStar."); 1338 } 1339 // Now we will create the Column object to return to the 1340 // AggQuerySpec. We will use the convertTable() method 1341 // because it is convenient and it is capable to convert 1342 // our base table into a series of parent-child tables 1343 // with their join paths figured out. 1344 DimTable columnTable = 1345 convertTable( 1346 AggStar.this.star.getColumn(bitPosition) 1347 .getTable(), 1348 usage); 1349 // Make sure to return the last child table, since 1350 // AggQuerySpec will take care of going up the 1351 // parent-child hierarchy and do all the work for us. 1352 while (columnTable.getChildTables().size() > 0) { 1353 columnTable = columnTable.getChildTables().get(0); 1354 } 1355 final DimTable finalColumnTable = columnTable; 1356 levelColumnsToJoin.put( 1357 bitPos, 1358 new Column( 1359 ((MondrianDef.Column)parentLevel.getKeyExp()) 1360 .getColumnName(), 1361 parentLevel.getKeyExp(), 1362 AggStar.this.star.getColumn(bitPos).getDatatype(), 1363 bitPos) 1364 { 1365 public Table getTable() { 1366 return finalColumnTable; 1367 } 1368 }); 1369 // Do the next parent level. 1370 parentLevel = (RolapLevel) parentLevel.getParentLevel(); 1371 } 1372 } 1373 } 1374 1375 public void print(final PrintWriter pw, final String prefix) { 1376 pw.print(prefix); 1377 pw.println("Table:"); 1378 String subprefix = prefix + " "; 1379 String subsubprefix = subprefix + " "; 1380 1381 pw.print(subprefix); 1382 pw.print("name="); 1383 pw.println(getName()); 1384 1385 if (getRelation() != null) { 1386 pw.print(subprefix); 1387 pw.print("relation="); 1388 pw.println(getRelation()); 1389 } 1390 1391 pw.print(subprefix); 1392 pw.print("numberofrows="); 1393 pw.println(getNumberOfRows()); 1394 1395 pw.print(subprefix); 1396 pw.println("FactCount:"); 1397 factCountColumn.print(pw, subsubprefix); 1398 pw.println(); 1399 1400 pw.print(subprefix); 1401 pw.println("Measures:"); 1402 for (Measure column : getMeasures()) { 1403 column.print(pw, subsubprefix); 1404 pw.println(); 1405 } 1406 1407 pw.print(subprefix); 1408 pw.println("Levels:"); 1409 for (Level level : getLevels()) { 1410 level.print(pw, subsubprefix); 1411 pw.println(); 1412 } 1413 1414 for (DimTable child : getChildTables()) { 1415 child.print(pw, subprefix); 1416 } 1417 } 1418 1419 private void makeNumberOfRows() { 1420 if (approxRowCount >= 0) { 1421 numberOfRows = approxRowCount; 1422 return; 1423 } 1424 SqlQuery query = getSqlQuery(); 1425 query.addSelect("count(*)", null); 1426 query.addFrom(getRelation(), getName(), false); 1427 DataSource dataSource = getAggStar().getStar().getDataSource(); 1428 SqlStatement stmt = 1429 RolapUtil.executeQuery( 1430 dataSource, 1431 query.toString(), 1432 new Locus( 1433 new Execution( 1434 star.getSchema().getInternalConnection() 1435 .getInternalStatement(), 1436 0), 1437 "AggStar.FactTable.makeNumberOfRows", 1438 "Counting rows in aggregate table")); 1439 try { 1440 ResultSet resultSet = stmt.getResultSet(); 1441 if (resultSet.next()) { 1442 ++stmt.rowCount; 1443 numberOfRows = resultSet.getInt(1); 1444 } else { 1445 getLogger().warn( 1446 mres.SqlQueryFailed.str( 1447 "AggStar.FactTable.makeNumberOfRows", 1448 query.toString())); 1449 1450 // set to large number so that this table is never used 1451 numberOfRows = Integer.MAX_VALUE / getTotalColumnSize(); 1452 } 1453 } catch (SQLException e) { 1454 throw stmt.handle(e); 1455 } finally { 1456 stmt.close(); 1457 } 1458 } 1459 } 1460 1461 /** 1462 * This class represents a dimension table. 1463 */ 1464 public class DimTable extends Table { 1465 private final Table parent; 1466 private final JoinCondition joinCondition; 1467 1468 DimTable( 1469 final Table parent, 1470 final String name, 1471 final MondrianDef.Relation relation, 1472 final JoinCondition joinCondition) 1473 { 1474 super(name, relation); 1475 this.parent = parent; 1476 this.joinCondition = joinCondition; 1477 } 1478 1479 public Table getParent() { 1480 return parent; 1481 } 1482 1483 public boolean hasParent() { 1484 return true; 1485 } 1486 1487 public boolean hasJoinCondition() { 1488 return true; 1489 } 1490 1491 public Table.JoinCondition getJoinCondition() { 1492 return joinCondition; 1493 } 1494 1495 /** 1496 * Add all of this Table's columns to the list parameter and then add 1497 * all child table columns. 1498 */ 1499 public void addColumnsToList(final List<Column> list) { 1500 list.addAll(levels); 1501 for (DimTable dimTable : getChildTables()) { 1502 dimTable.addColumnsToList(list); 1503 } 1504 } 1505 1506 public void print(final PrintWriter pw, final String prefix) { 1507 pw.print(prefix); 1508 pw.println("Table:"); 1509 String subprefix = prefix + " "; 1510 String subsubprefix = subprefix + " "; 1511 1512 pw.print(subprefix); 1513 pw.print("name="); 1514 pw.println(getName()); 1515 1516 if (getRelation() != null) { 1517 pw.print(subprefix); 1518 pw.print("relation="); 1519 pw.println(getRelation()); 1520 } 1521 1522 pw.print(subprefix); 1523 pw.println("Levels:"); 1524 1525 for (Level level : getLevels()) { 1526 level.print(pw, subsubprefix); 1527 pw.println(); 1528 } 1529 1530 joinCondition.print(pw, subprefix); 1531 1532 for (DimTable child : getChildTables()) { 1533 child.print(pw, subprefix); 1534 } 1535 } 1536 } 1537 1538 public String toString() { 1539 StringWriter sw = new StringWriter(256); 1540 PrintWriter pw = new PrintWriter(sw); 1541 print(pw, ""); 1542 pw.flush(); 1543 return sw.toString(); 1544 } 1545 1546 /** 1547 * Print this AggStar. 1548 */ 1549 public void print(final PrintWriter pw, final String prefix) { 1550 pw.print(prefix); 1551 pw.println("AggStar:" + getFactTable().getName()); 1552 String subprefix = prefix + " "; 1553 1554 pw.print(subprefix); 1555 pw.print(" bk="); 1556 pw.println(bitKey); 1557 1558 pw.print(subprefix); 1559 pw.print("fbk="); 1560 pw.println(levelBitKey); 1561 1562 pw.print(subprefix); 1563 pw.print("mbk="); 1564 pw.println(measureBitKey); 1565 1566 pw.print(subprefix); 1567 pw.print("has foreign key="); 1568 pw.println(aggTable.hasChildren()); 1569 1570 for (AggStar.Table.Column column 1571 : getFactTable().getColumns()) 1572 { 1573 pw.print(" "); 1574 pw.println(column); 1575 } 1576 1577 aggTable.print(pw, subprefix); 1578 } 1579} 1580 1581// End AggStar.java