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