KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > aggmatcher > AggStar


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/AggStar.java#27 $
3 // This software is subject to the terms of the Common Public License
4 // Agreement, available at the following URL:
5 // http://www.opensource.org/licenses/cpl.html.
6 // Copyright (C) 2005-2007 Julian Hyde and others
7 // All Rights Reserved.
8 // You must accept the terms of that agreement to use this software.
9 */

10
11 package mondrian.rolap.aggmatcher;
12
13 import mondrian.olap.*;
14 import mondrian.resource.MondrianResource;
15 import mondrian.recorder.MessageRecorder;
16 import mondrian.rolap.*;
17 import mondrian.rolap.sql.SqlQuery;
18 import org.apache.log4j.Logger;
19
20 import javax.sql.DataSource JavaDoc;
21 import java.io.PrintWriter JavaDoc;
22 import java.io.StringWriter JavaDoc;
23 import java.sql.*;
24 import java.util.*;
25
26 /**
27  * This is an aggregate table version of a RolapStar for a fact table.
28  * <p>
29  * There is the following class structure:
30  * <pre>
31  * AggStar
32  * Table
33  * JoinCondition
34  * Column
35  * Level extends Column
36  * FactTable extends Table
37  * Measure extends Table.Column
38  * DimTable extends Table
39  * <pre>
40  * Each inner class is non-static meaning that instances have implied references
41  * to the enclosing object.
42  *
43  * @author Richard M. Emberson
44  * @version $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/AggStar.java#27 $
45  */

46 public class AggStar {
47     private static final Logger LOGGER = Logger.getLogger(AggStar.class);
48
49     static Logger getLogger() {
50         return LOGGER;
51     }
52
53     private static final MondrianResource mres = MondrianResource.instance();
54
55     /**
56      * Creates an AggStar and all of its {@link Table}, {@link Table.Column}s,
57      * etc.
58      */

59     public static AggStar makeAggStar(
60             final RolapStar star,
61             final JdbcSchema.Table dbTable,
62             final MessageRecorder msgRecorder) {
63
64         AggStar aggStar = new AggStar(star, dbTable);
65         AggStar.FactTable aggStarFactTable = aggStar.getFactTable();
66
67         // 1. load fact count
68
for (Iterator<JdbcSchema.Table.Column.Usage> it =
69             dbTable.getColumnUsages(JdbcSchema.UsageType.FACT_COUNT);
70                     it.hasNext();) {
71             JdbcSchema.Table.Column.Usage usage = it.next();
72             aggStarFactTable.loadFactCount(usage);
73         }
74
75         // 2. load measures
76
for (Iterator<JdbcSchema.Table.Column.Usage> it =
77                 dbTable.getColumnUsages(JdbcSchema.UsageType.MEASURE);
78                 it.hasNext();) {
79             JdbcSchema.Table.Column.Usage usage = it.next();
80             aggStarFactTable.loadMeasure(usage);
81         }
82
83         // 3. load foreign keys
84
for (Iterator<JdbcSchema.Table.Column.Usage> it =
85                 dbTable.getColumnUsages(JdbcSchema.UsageType.FOREIGN_KEY);
86                 it.hasNext();) {
87             JdbcSchema.Table.Column.Usage usage = it.next();
88             aggStarFactTable.loadForeignKey(usage);
89         }
90
91         // 4. load levels
92
for (Iterator<JdbcSchema.Table.Column.Usage> it =
93                 dbTable.getColumnUsages(JdbcSchema.UsageType.LEVEL);
94                 it.hasNext();) {
95             JdbcSchema.Table.Column.Usage usage = it.next();
96             aggStarFactTable.loadLevel(usage);
97         }
98
99         // 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     public AggStar.FactTable getFactTable() {
208         return aggTable;
209     }
210
211     /**
212      * Returns a measure of the IO cost of querying this table. It can be
213      * either the row count or the row count times the size of a row.
214      * If the property {@link MondrianProperties#ChooseAggregateByVolume}
215      * is true, then volume is returned, otherwise row count.
216      */

217     public int getSize() {
218         return MondrianProperties.instance().ChooseAggregateByVolume.get() ?
219                 getFactTable().getVolume() :
220                 getFactTable().getNumberOfRows();
221     }
222
223     void setForeignKey(int index) {
224         this.foreignKeyBitKey.set(index);
225     }
226     public BitKey getForeignKeyBitKey() {
227         return this.foreignKeyBitKey;
228     }
229
230     /**
231      * Is this AggStar's BitKey a super set (proper or not) of the BitKey
232      * parameter.
233      *
234      * @param bitKey
235      * @return true if it is a super set
236      */

237     public boolean superSetMatch(final BitKey bitKey) {
238         return getBitKey().isSuperSetOf(bitKey);
239     }
240
241     /**
242      * Return true if this AggStar's level BitKey equals the
243      * <code>levelBitKey</code> parameter
244      * and if this AggStar's measure BitKey is a super set
245      * (proper or not) of the <code>measureBitKey</code> parameter.
246      */

247     public boolean select(
248             final BitKey levelBitKey,
249             final BitKey coreLevelBitKey,
250             final BitKey measureBitKey) {
251         if (!getMeasureBitKey().isSuperSetOf(measureBitKey)) {
252             return false;
253         }
254         if (getLevelBitKey().equals(levelBitKey)) {
255             return true;
256         } else if (getLevelBitKey().isSuperSetOf(levelBitKey) &&
257                 getLevelBitKey().andNot(coreLevelBitKey).equals(
258                         levelBitKey.andNot(coreLevelBitKey))) {
259             // It's OK to roll up levels which are orthogonal to the distinct
260
// measure.
261
return true;
262         } else {
263             return false;
264         }
265     }
266
267     /**
268      * Get this AggStar's RolapStar.
269      */

270     public RolapStar getStar() {
271         return star;
272     }
273
274     /**
275      * Return true if AggStar has measures
276      */

277     public boolean hasMeasures() {
278         return getFactTable().hasMeasures();
279     }
280
281     /**
282      * Return true if AggStar has levels
283      */

284     public boolean hasLevels() {
285         return getFactTable().hasLevels();
286     }
287
288     /**
289      * Returns whether this AggStar has foreign keys.
290      */

291     public boolean hasForeignKeys() {
292         return getFactTable().hasChildren();
293     }
294
295     /**
296      * Returns the BitKey.
297      */

298     public BitKey getBitKey() {
299         return bitKey;
300     }
301
302     /**
303      * Get the foreign-key/level BitKey.
304      */

305     public BitKey getLevelBitKey() {
306         return levelBitKey;
307     }
308
309     /**
310      * Returns a BitKey of all measures.
311      */

312     public BitKey getMeasureBitKey() {
313         return measureBitKey;
314     }
315
316     /**
317      * Returns a BitKey containing only distinct measures.
318      */

319     public BitKey getDistinctMeasureBitKey() {
320         return distinctMeasureBitKey;
321     }
322
323     /**
324      * Get an SqlQuery instance.
325      */

326     private SqlQuery getSqlQuery() {
327         return getStar().getSqlQuery();
328     }
329
330     /**
331      * Get the Measure at the given bit position or return null.
332      * Note that there is no check that the bit position is within the range of
333      * the array of columns.
334      * Nor is there a check that the column type at that position is a Measure.
335      *
336      * @param bitPos
337      * @return A Measure or null.
338      */

339     public AggStar.FactTable.Measure lookupMeasure(final int bitPos) {
340         AggStar.Table.Column column = lookupColumn(bitPos);
341         return (column instanceof AggStar.FactTable.Measure)
342             ? (AggStar.FactTable.Measure) column
343             : null;
344     }
345     /**
346      * Get the Level at the given bit position or return null.
347      * Note that there is no check that the bit position is within the range of
348      * the array of columns.
349      * Nor is there a check that the column type at that position is a Level.
350      *
351      * @param bitPos
352      * @return A Level or null.
353      */

354     public AggStar.Table.Level lookupLevel(final int bitPos) {
355         AggStar.Table.Column column = lookupColumn(bitPos);
356         return (column instanceof AggStar.FactTable.Level)
357             ? (AggStar.FactTable.Level) column
358             : null;
359     }
360
361     /**
362      * Get the Column at the bit position.
363      * Note that there is no check that the bit position is within the range of
364      * the array of columns.
365      */

366     public AggStar.Table.Column lookupColumn(final int bitPos) {
367         return columns[bitPos];
368     }
369
370     /**
371      * This is called by within the Column constructor.
372      *
373      * @param column
374      */

375     private void addColumn(final AggStar.Table.Column column) {
376         columns[column.getBitPosition()] = column;
377     }
378
379     private static final Logger JOIN_CONDITION_LOGGER =
380             Logger.getLogger(AggStar.Table.JoinCondition.class);
381
382     /**
383      * Base Table class for the FactTable and DimTable classes.
384      * This class parallels the RolapStar.Table class.
385      *
386      */

387     public abstract class Table {
388
389         /**
390          * The query join condition between a base table and this table (the
391          * table that owns the join condition).
392          */

393         public class JoinCondition {
394             // I think this is always a MondrianDef.Column
395
private final MondrianDef.Expression left;
396             private final MondrianDef.Expression right;
397
398             private JoinCondition(final MondrianDef.Expression left,
399                                   final MondrianDef.Expression right) {
400                 if (!(left instanceof MondrianDef.Column)) {
401                     JOIN_CONDITION_LOGGER.debug(
402                         "JoinCondition.left NOT Column: "
403                         +left.getClass().getName());
404                 }
405                 this.left = left;
406                 this.right = right;
407             }
408
409             /**
410              * Get the enclosing AggStar.Table.
411              */

412             public Table getTable() {
413                 return AggStar.Table.this;
414             }
415
416             /**
417              * This is used to create part of a SQL where clause.
418              */

419             String JavaDoc toString(final SqlQuery query) {
420                 StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
421                 buf.append(left.getExpression(query));
422                 buf.append(" = ");
423                 buf.append(right.getExpression(query));
424                 return buf.toString();
425             }
426             public String JavaDoc toString() {
427                 StringWriter JavaDoc sw = new StringWriter JavaDoc(128);
428                 PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
429                 print(pw, "");
430                 pw.flush();
431                 return sw.toString();
432             }
433
434             /**
435              * Prints this table and its children.
436              */

437             public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
438                 SqlQuery sqlQueuy = getTable().getSqlQuery();
439                 pw.print(prefix);
440                 pw.println("JoinCondition:");
441                 String JavaDoc subprefix = prefix + " ";
442
443                 pw.print(subprefix);
444                 pw.print("left=");
445                 if (left instanceof MondrianDef.Column) {
446                     MondrianDef.Column c = (MondrianDef.Column) left;
447                     mondrian.rolap.RolapStar.Column col = getTable().getAggStar().getStar().getFactTable().lookupColumn(c.name);
448                     if (col != null) {
449                         pw.print(" (");
450                         pw.print(col.getBitPosition());
451                         pw.print(") ");
452                     }
453                 }
454                 pw.println(left.getExpression(sqlQueuy));
455
456                 pw.print(subprefix);
457                 pw.print("right=");
458                 pw.println(right.getExpression(sqlQueuy));
459             }
460         }
461
462
463         /**
464          * Base class for Level and Measure classes
465          */

466         public class Column {
467
468             private final String JavaDoc name;
469             private final MondrianDef.Expression expression;
470             private final SqlQuery.Datatype datatype;
471             /**
472              * This is only used in RolapAggregationManager and adds
473              * non-constraining columns making the drill-through queries
474              * easier for humans to understand.
475              */

476             private final Column nameColumn;
477
478             /** this has a unique value per star */
479             private final int bitPosition;
480
481             protected Column(
482                     final String JavaDoc name,
483                     final MondrianDef.Expression expression,
484                     final SqlQuery.Datatype datatype,
485                     final int bitPosition) {
486                 this.name = name;
487                 this.expression = expression;
488                 this.datatype = datatype;
489                 this.bitPosition = bitPosition;
490
491                 this.nameColumn = null;
492
493                 // do not count the fact_count column
494
if (bitPosition >= 0) {
495                     AggStar.this.bitKey.set(bitPosition);
496                     AggStar.this.addColumn(this);
497                 }
498             }
499
500             /**
501              * Get the name of the column (this is the name in the database).
502              */

503             public String JavaDoc getName() {
504                 return name;
505             }
506
507             /**
508              * Get the enclosing AggStar.Table.
509              */

510             public AggStar.Table getTable() {
511                 return AggStar.Table.this;
512             }
513
514             /**
515              * Get the bit possition associted with this column. This has the
516              * same value as this column's RolapStar.Column.
517              */

518             public int getBitPosition() {
519                 return bitPosition;
520             }
521
522             /**
523              * Returns the datatype of this column.
524              */

525             public SqlQuery.Datatype getDatatype() {
526                 return datatype;
527             }
528
529             public SqlQuery getSqlQuery() {
530                 return getTable().getAggStar().getSqlQuery();
531             }
532
533             public MondrianDef.Expression getExpression() {
534                 return expression;
535             }
536
537             /**
538              * Generates a SQL expression, which typically this looks like
539              * this: <code><i>tableName</i>.<i>columnName</i></code>.
540              */

541             public String JavaDoc generateExprString(final SqlQuery query) {
542                 return getExpression().getExpression(query);
543             }
544
545             public String JavaDoc toString() {
546                 StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
547                 PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
548                 print(pw, "");
549                 pw.flush();
550                 return sw.toString();
551             }
552             public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
553                 SqlQuery sqlQuery = getSqlQuery();
554                 pw.print(prefix);
555                 pw.print(getName());
556                 pw.print(" (");
557                 pw.print(getBitPosition());
558                 pw.print("): ");
559                 pw.print(generateExprString(sqlQuery));
560             }
561         }
562
563         /**
564          * This class is used for holding foreign key columns.
565          * Both DimTables and FactTables can have Level columns.
566          */

567         final class ForeignKey extends Column {
568
569             private ForeignKey(
570                     final String JavaDoc name,
571                     final MondrianDef.Expression expression,
572                     final SqlQuery.Datatype datatype,
573                     final int bitPosition) {
574                 super(name, expression, datatype, bitPosition);
575                 AggStar.this.levelBitKey.set(bitPosition);
576             }
577         }
578
579         /**
580          * This class is used for holding dimension level information.
581          * Both DimTables and FactTables can have Level columns.
582          */

583         final class Level extends Column {
584             private final RolapStar.Column starColumn;
585
586             private Level(
587                     final String JavaDoc name,
588                     final MondrianDef.Expression expression,
589                     final int bitPosition,
590                     RolapStar.Column starColumn) {
591                 super(name, expression, starColumn.getDatatype(), bitPosition);
592                 this.starColumn = starColumn;
593                 AggStar.this.levelBitKey.set(bitPosition);
594             }
595         }
596
597         /** The name of the table in the database. */
598         private final String JavaDoc name;
599         private final MondrianDef.Relation relation;
600         protected final List<Level> levels = new ArrayList<Level>();
601         protected List<DimTable> children;
602
603         Table(final String JavaDoc name, final MondrianDef.Relation relation) {
604             this.name = name;
605             this.relation = relation;
606             this.children = Collections.emptyList();
607         }
608
609         /**
610          * Return the name of the table in the database.
611          */

612         public String JavaDoc getName() {
613             return name;
614         }
615
616         /**
617          * Return true if this table has a parent table (FactTable instances
618          * do not have parent tables, all other do).
619          */

620         public abstract boolean hasParent();
621
622         /**
623          * Get the parent table (returns null if this table is a FactTable).
624          */

625         public abstract Table getParent();
626
627         /**
628          * Return true if this table has a join condition (only DimTables have
629          * join conditions, FactTable instances do not).
630          */

631         public abstract boolean hasJoinCondition();
632         public abstract Table.JoinCondition getJoinCondition();
633
634         public MondrianDef.Relation getRelation() {
635             return relation;
636         }
637
638         /**
639          * Get this table's enclosing AggStar.
640          */

641         protected AggStar getAggStar() {
642             return AggStar.this;
643         }
644
645         /**
646          * Get a SqlQuery object.
647          */

648         protected SqlQuery getSqlQuery() {
649             return getAggStar().getSqlQuery();
650         }
651
652         /**
653          * Add a Level column.
654          *
655          * @param level
656          */

657         protected void addLevel(final AggStar.Table.Level level) {
658             this.levels.add(level);
659         }
660
661         /**
662          * Returns all level columns.
663          */

664         public List<Level> getLevels() {
665             return levels;
666         }
667
668         /**
669          * Return true if table has levels.
670          */

671         public boolean hasLevels() {
672             return ! levels.isEmpty();
673         }
674
675         /**
676          * Add a child DimTable table.
677          *
678          * @param child
679          */

680         protected void addTable(final DimTable child) {
681             if (children == Collections.EMPTY_LIST) {
682                 children = new ArrayList<DimTable>();
683             }
684             children.add(child);
685         }
686
687         /**
688          * Returns a list of child {@link Table} objects.
689          */

690         public List<DimTable> getChildTables() {
691             return children;
692         }
693
694         /**
695          * Return true if this table has one or more child tables.
696          */

697         public boolean hasChildren() {
698             return ! children.isEmpty();
699         }
700
701         /**
702          * Converts a {@link mondrian.rolap.RolapStar.Table} into a
703          * {@link AggStar.DimTable} as well as converting all columns and
704          * child tables. If the rightJoinConditionColumnName parameter
705          * is null, then the table's namd and the rTable parameter's
706          * condition left condition's column name are used to form the
707          * join condition's left expression.
708          */

709         protected AggStar.DimTable convertTable(
710                 final RolapStar.Table rTable,
711                 final String JavaDoc rightJoinConditionColumnName) {
712             String JavaDoc tableName = rTable.getAlias();
713             MondrianDef.Relation relation = rTable.getRelation();
714             RolapStar.Condition rjoinCondition = rTable.getJoinCondition();
715             MondrianDef.Expression rleft = rjoinCondition.getLeft();
716             MondrianDef.Expression rright = rjoinCondition.getRight();
717
718             MondrianDef.Column left = null;
719             if (rightJoinConditionColumnName != null) {
720                 left = new MondrianDef.Column(getName(),
721                                               rightJoinConditionColumnName);
722             } else {
723                 if (rleft instanceof MondrianDef.Column) {
724                     MondrianDef.Column lcolumn = (MondrianDef.Column) rleft;
725                     left = new MondrianDef.Column(getName(), lcolumn.name);
726                 } else {
727
728                     // RME TODO can we catch this during validation
729
String JavaDoc msg = mres.BadRolapStarLeftJoinCondition.str(
730                         "AggStar.Table",
731                         rleft.getClass().getName(), left.toString());
732                     getLogger().warn(msg);
733                 }
734             }
735             // Explicitly set which columns are foreign keys in the
736
// AggStar. This lets us later determine if a measure is
737
// based upon a foreign key (see AggregationManager findAgg
738
// method).
739
mondrian.rolap.RolapStar.Column col =
740                 getAggStar().getStar().getFactTable().lookupColumn(left.name);
741             if (col != null) {
742                 getAggStar().setForeignKey(col.getBitPosition());
743             }
744             JoinCondition joinCondition = new JoinCondition(left, rright);
745             DimTable dimTable =
746                 new DimTable(this, tableName, relation, joinCondition);
747
748             dimTable.convertColumns(rTable);
749             dimTable.convertChildren(rTable);
750
751             return dimTable;
752         }
753
754         /**
755          * Convert a RolapStar.Table table's columns into
756          * AggStar.Table.Level columns.
757          *
758          * @param rTable
759          */

760         protected void convertColumns(final RolapStar.Table rTable) {
761             // add level columns
762
for (RolapStar.Column column : rTable.getColumns()) {
763                 String JavaDoc name = column.getName();
764                 MondrianDef.Expression expression = column.getExpression();
765                 int bitPosition = column.getBitPosition();
766
767                 Level level = new Level(
768                     name,
769                     expression,
770                     bitPosition,
771                     column);
772                 addLevel(level);
773             }
774         }
775
776         /**
777          * Convert the child tables of a RolapStar.Table into
778          * child AggStar.DimTable tables.
779          *
780          * @param rTable
781          */

782         protected void convertChildren(final RolapStar.Table rTable) {
783             // add children tables
784
for (RolapStar.Table rTableChild : rTable.getChildren()) {
785                 DimTable dimChild = convertTable(rTableChild, null);
786
787                 addTable(dimChild);
788             }
789         }
790
791         /**
792          * This is a copy of the code found in RolapStar used to generate an SQL
793          * query.
794          *
795          * @param query
796          * @param failIfExists
797          * @param joinToParent
798          */

799         public void addToFrom(final SqlQuery query,
800                               final boolean failIfExists,
801                               final boolean joinToParent) {
802             query.addFrom(relation, name, failIfExists);
803             if (joinToParent) {
804                 if (hasParent()) {
805                     getParent().addToFrom(query, failIfExists, joinToParent);
806                 }
807                 if (hasJoinCondition()) {
808                     query.addWhere(getJoinCondition().toString(query));
809                 }
810             }
811         }
812
813         public String JavaDoc toString() {
814             StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
815             PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
816             print(pw, "");
817             pw.flush();
818             return sw.toString();
819         }
820         public abstract void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix);
821     }
822
823     /**
824      * This is an aggregate fact table.
825      */

826     public class FactTable extends Table {
827
828         /**
829          * This is a Column that is a Measure (contains an aggregator).
830          */

831         public class Measure extends Table.Column {
832             private final RolapAggregator aggregator;
833             /**
834              * The fact table column which is being aggregated.
835              */

836             private final MondrianDef.Expression argument;
837             /**
838              * For distinct-count measures, contains a bitKey of levels which
839              * it is OK to roll up. For regular measures, this is empty, since
840              * all levels can be rolled up.
841              */

842             private final BitKey rollableLevelBitKey;
843
844             private Measure(
845                     final String JavaDoc name,
846                     final MondrianDef.Expression expression,
847                     final SqlQuery.Datatype datatype,
848                     final int bitPosition,
849                     final RolapAggregator aggregator,
850                     final MondrianDef.Expression argument) {
851                 super(name, expression, datatype, bitPosition);
852                 this.aggregator = aggregator;
853                 this.argument = argument;
854                 assert (argument != null) == aggregator.isDistinct();
855                 this.rollableLevelBitKey =
856                         BitKey.Factory.makeBitKey(star.getColumnCount());
857
858                 AggStar.this.measureBitKey.set(bitPosition);
859             }
860
861             public boolean isDistinct() {
862                 return aggregator.isDistinct();
863             }
864
865             /**
866              * Get this Measure's RolapAggregator.
867              */

868             public RolapAggregator getAggregator() {
869                 return aggregator;
870             }
871
872             /**
873              * Returns a <code>BitKey</code> of the levels which can be
874              * safely rolled up. (For distinct-count measures, most can't.)
875              */

876             public BitKey getRollableLevelBitKey() {
877                 return rollableLevelBitKey;
878             }
879
880             /**
881              * Generates an expression to rollup this measure from a previous
882              * result. For example, a "COUNT" and "DISTINCT COUNT" measures
883              * rollup using "SUM".
884              */

885 /*
886             public String generateRollupString(SqlQuery query) {
887                 String expr = generateExprString(query);
888                 final Aggregator rollup = getAggregator().getRollup();
889                 return ((RolapAggregator) rollup).getExpression(expr);
890             }
891 */

892 /*
893             public String generateRollupString(SqlQuery query) {
894                 String expr = generateExprString(query);
895                 // final Aggregator rollup = getAggregator().getRollup();
896                 Aggregator rollup = (getAggregator().isDistinct())
897                     ? getAggregator().getNonDistinctAggregator()
898                     : getAggregator().getRollup();
899
900                 String s = ((RolapAggregator) rollup).getExpression(expr);
901                 return s;
902             }
903 */

904             public String JavaDoc generateRollupString(SqlQuery query) {
905                 String JavaDoc expr = generateExprString(query);
906                 Aggregator rollup = null;
907
908                 BitKey fkbk = AggStar.this.getForeignKeyBitKey();
909                 // When rolling up and the aggregator is distinct and
910
// the measure is based upon a foreign key, then
911
// one must use "count" rather than "sum"
912
if (fkbk.get(getBitPosition())) {
913                     rollup = (getAggregator().isDistinct())
914                         ? getAggregator().getNonDistinctAggregator()
915                         : getAggregator().getRollup();
916                 } else {
917                     rollup = getAggregator().getRollup();
918                 }
919
920                 String JavaDoc s = ((RolapAggregator) rollup).getExpression(expr);
921                 return s;
922             }
923             public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
924                 SqlQuery sqlQuery = getSqlQuery();
925                 pw.print(prefix);
926                 pw.print(getName());
927                 pw.print(" (");
928                 pw.print(getBitPosition());
929                 pw.print("): ");
930                 pw.print(generateRollupString(sqlQuery));
931             }
932         }
933
934         private Column factCountColumn;
935         private final List<Measure> measures;
936         private final int totalColumnSize;
937         private int numberOfRows;
938
939         FactTable(final JdbcSchema.Table aggTable) {
940             this(aggTable.getName(),
941                  aggTable.table,
942                  aggTable.getTotalColumnSize(),
943                  aggTable.getNumberOfRows());
944         }
945         FactTable(final String JavaDoc name,
946                   final MondrianDef.Relation relation,
947                   final int totalColumnSize,
948                   final int numberOfRows) {
949             super(name, relation);
950             this.totalColumnSize = totalColumnSize;
951             this.measures = new ArrayList<Measure>();
952             this.numberOfRows = numberOfRows;
953         }
954         public Table getParent() {
955             return null;
956         }
957         public boolean hasParent() {
958             return false;
959         }
960         public boolean hasJoinCondition() {
961             return false;
962         }
963         public Table.JoinCondition getJoinCondition() {
964             return null;
965         }
966
967         /**
968          * Get the volume of the table (now of rows * size of a row).
969          */

970         public int getVolume() {
971             return getTotalColumnSize() * getNumberOfRows();
972         }
973
974         /**
975          * Get the total size of all columns in a row.
976          */

977         public int getTotalColumnSize() {
978             return totalColumnSize;
979         }
980
981         /**
982          * Get the number of rows in this aggregate table.
983          */

984         public int getNumberOfRows() {
985             if (numberOfRows == -1) {
986                 makeNumberOfRows();
987             }
988             return numberOfRows;
989         }
990
991         /**
992          * This is for testing ONLY.
993          *
994          * @param numberOfRows
995          */

996         void setNumberOfRows(int numberOfRows) {
997             this.numberOfRows = numberOfRows;
998         }
999
1000        /**
1001         * Returns a list of all measures.
1002         */

1003        public List<Measure> getMeasures() {
1004            return measures;
1005        }
1006
1007        /**
1008         * Return true it table has measures
1009         */

1010        public boolean hasMeasures() {
1011            return ! measures.isEmpty();
1012        }
1013
1014        /**
1015         * Returns a list of the columns in this table.
1016         */

1017        public List<Column> getColumns() {
1018            List<Column> list = new ArrayList<Column>();
1019            list.addAll(measures);
1020            list.addAll(levels);
1021            for (DimTable dimTable : getChildTables()) {
1022                dimTable.addColumnsToList(list);
1023            }
1024            return list;
1025        }
1026
1027        /**
1028         * For a foreign key usage create a child DimTable table.
1029         *
1030         * @param usage
1031         */

1032        private void loadForeignKey(final JdbcSchema.Table.Column.Usage usage) {
1033            if (usage.rTable != null) {
1034                DimTable child = convertTable(
1035                        usage.rTable,
1036                        usage.rightJoinConditionColumnName);
1037                addTable(child);
1038            } else {
1039                // it a column thats not a measure or foreign key - it must be
1040
// a non-shared dimension
1041
// See: AggTableManager.java
1042
JdbcSchema.Table.Column column = usage.getColumn();
1043                String JavaDoc name = column.getName();
1044                String JavaDoc symbolicName = usage.getSymbolicName();
1045                if (symbolicName == null) {
1046                    symbolicName = name;
1047                }
1048
1049                MondrianDef.Expression expression =
1050                    new MondrianDef.Column(getName(), name);
1051                SqlQuery.Datatype datatype = column.getDatatype();
1052                RolapStar.Column rColumn = usage.rColumn;
1053                if (rColumn == null) {
1054                    String JavaDoc msg = "loadForeignKey: for column " +
1055                        name +
1056                        ", rColumn == null";
1057                    getLogger().warn(msg);
1058                } else {
1059                    int bitPosition = rColumn.getBitPosition();
1060                    ForeignKey c =
1061                        new ForeignKey(
1062                            symbolicName, expression, datatype, bitPosition);
1063                    getAggStar().setForeignKey(c.getBitPosition());
1064                }
1065            }
1066        }
1067
1068        /**
1069         * Given a usage of type measure, create a Measure column.
1070         *
1071         * @param usage
1072         */

1073        private void loadMeasure(final JdbcSchema.Table.Column.Usage usage) {
1074            final JdbcSchema.Table.Column column = usage.getColumn();
1075            String JavaDoc name = column.getName();
1076            String JavaDoc symbolicName = usage.getSymbolicName();
1077            if (symbolicName == null) {
1078                symbolicName = name;
1079            }
1080            SqlQuery.Datatype datatype = column.getDatatype();
1081            RolapAggregator aggregator = usage.getAggregator();
1082
1083            MondrianDef.Expression expression;
1084            if (column.hasUsage(JdbcSchema.UsageType.FOREIGN_KEY) &&
1085                    ! aggregator.isDistinct()) {
1086                expression = factCountColumn.getExpression();
1087            } else {
1088                expression = new MondrianDef.Column(getName(), name);
1089            }
1090
1091            MondrianDef.Expression argument;
1092            if (aggregator.isDistinct()) {
1093                argument = usage.rMeasure.getExpression();
1094            } else {
1095                argument = null;
1096            }
1097
1098            int bitPosition = usage.rMeasure.getBitPosition();
1099
1100            Measure aggMeasure = new Measure(
1101                    symbolicName,
1102                    expression,
1103                    datatype,
1104                    bitPosition,
1105                    aggregator,
1106                    argument);
1107
1108            measures.add(aggMeasure);
1109
1110            if (aggMeasure.aggregator.isDistinct()) {
1111                distinctMeasureBitKey.set(bitPosition);
1112            }
1113        }
1114
1115        /**
1116         * Create a fact_count column for a usage of type fact count.
1117         *
1118         * @param usage
1119         */

1120        private void loadFactCount(final JdbcSchema.Table.Column.Usage usage) {
1121            String JavaDoc name = usage.getColumn().getName();
1122            String JavaDoc symbolicName = usage.getSymbolicName();
1123            if (symbolicName == null) {
1124                symbolicName = name;
1125            }
1126
1127            MondrianDef.Expression expression =
1128                new MondrianDef.Column(getName(), name);
1129            SqlQuery.Datatype datatype = usage.getColumn().getDatatype();
1130            int bitPosition = -1;
1131
1132            Column aggColumn = new Column(
1133                    symbolicName,
1134                    expression,
1135                    datatype,
1136                    bitPosition);
1137
1138            factCountColumn = aggColumn;
1139        }
1140
1141        /**
1142         * Given a usage of type level, create a Level column.
1143         *
1144         * @param usage
1145         */

1146        private void loadLevel(final JdbcSchema.Table.Column.Usage usage) {
1147            String JavaDoc name = usage.getSymbolicName();
1148            MondrianDef.Expression expression =
1149                new MondrianDef.Column(getName(), usage.levelColumnName);
1150            int bitPosition = usage.rColumn.getBitPosition();
1151            Level level = new Level(
1152                    name,
1153                    expression,
1154                    bitPosition,
1155                    usage.rColumn);
1156            addLevel(level);
1157        }
1158
1159        public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
1160            pw.print(prefix);
1161            pw.println("Table:");
1162            String JavaDoc subprefix = prefix + " ";
1163            String JavaDoc subsubprefix = subprefix + " ";
1164
1165            pw.print(subprefix);
1166            pw.print("name=");
1167            pw.println(getName());
1168
1169            if (getRelation() != null) {
1170                pw.print(subprefix);
1171                pw.print("relation=");
1172                pw.println(getRelation());
1173            }
1174
1175            pw.print(subprefix);
1176            pw.print("numberofrows=");
1177            pw.println(getNumberOfRows());
1178
1179            pw.print(subprefix);
1180            pw.println("FactCount:");
1181            factCountColumn.print(pw, subsubprefix);
1182            pw.println();
1183
1184            pw.print(subprefix);
1185            pw.println("Measures:");
1186            for (Measure column : getMeasures()) {
1187                column.print(pw, subsubprefix);
1188                pw.println();
1189            }
1190
1191            pw.print(subprefix);
1192            pw.println("Levels:");
1193            for (Level level : getLevels()) {
1194                level.print(pw, subsubprefix);
1195                pw.println();
1196            }
1197
1198            for (DimTable child : getChildTables()) {
1199                child.print(pw, subprefix);
1200            }
1201        }
1202
1203        private void makeNumberOfRows() {
1204            SqlQuery query = getSqlQuery();
1205            query.addSelect("count(*)");
1206            query.addFrom(getRelation(), getName(), false);
1207            DataSource JavaDoc dataSource = getAggStar().getStar().getDataSource();
1208            SqlStatement stmt =
1209                RolapUtil.executeQuery(
1210                    dataSource, query.toString(),
1211                    "AggStar.FactTable.makeNumberOfRows",
1212                    "Counting rows in aggregate table");
1213            try {
1214                ResultSet resultSet = stmt.getResultSet();
1215                if (resultSet.next()) {
1216                    ++stmt.rowCount;
1217                    numberOfRows = resultSet.getInt(1);
1218                } else {
1219                    String JavaDoc msg =
1220                        mres.SqlQueryFailed.str(
1221                            "AggStar.FactTable.makeNumberOfRows",
1222                            query.toString());
1223                    getLogger().warn(msg);
1224
1225                    // set to large number so that this table is never used
1226
numberOfRows = Integer.MAX_VALUE / getTotalColumnSize();
1227                }
1228            } catch (SQLException e) {
1229                stmt.handle(e);
1230            } finally {
1231                stmt.close();
1232            }
1233        }
1234    }
1235
1236    /**
1237     * This class represents a dimension table.
1238     */

1239    public class DimTable extends Table {
1240        private final Table parent;
1241        private final JoinCondition joinCondition;
1242
1243        DimTable(final Table parent,
1244                final String JavaDoc name,
1245                final MondrianDef.Relation relation,
1246                final JoinCondition joinCondition) {
1247            super(name, relation);
1248            this.parent = parent;
1249            this.joinCondition = joinCondition;
1250        }
1251        public Table getParent() {
1252            return parent;
1253        }
1254        public boolean hasParent() {
1255            return true;
1256        }
1257        public boolean hasJoinCondition() {
1258            return true;
1259        }
1260        public Table.JoinCondition getJoinCondition() {
1261            return joinCondition;
1262        }
1263
1264        /**
1265         * Add all of this Table's columns to the list parameter and then add
1266         * all child table columns.
1267         *
1268         * @param list
1269         */

1270        public void addColumnsToList(final List<Column> list) {
1271            list.addAll(levels);
1272            for (DimTable dimTable : getChildTables()) {
1273                dimTable.addColumnsToList(list);
1274            }
1275        }
1276
1277        public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
1278            pw.print(prefix);
1279            pw.println("Table:");
1280            String JavaDoc subprefix = prefix + " ";
1281            String JavaDoc subsubprefix = subprefix + " ";
1282
1283            pw.print(subprefix);
1284            pw.print("name=");
1285            pw.println(getName());
1286
1287            if (getRelation() != null) {
1288                pw.print(subprefix);
1289                pw.print("relation=");
1290                pw.println(getRelation());
1291            }
1292
1293            pw.print(subprefix);
1294            pw.println("Levels:");
1295
1296            for (Level level : getLevels()) {
1297                level.print(pw, subsubprefix);
1298                pw.println();
1299            }
1300
1301            joinCondition.print(pw, subprefix);
1302
1303            for (DimTable child : getChildTables()) {
1304                child.print(pw, subprefix);
1305            }
1306        }
1307    }
1308
1309    public String JavaDoc toString() {
1310        StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
1311        PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
1312        print(pw, "");
1313        pw.flush();
1314        return sw.toString();
1315    }
1316
1317    /**
1318     * Print this AggStar.
1319     *
1320     * @param pw
1321     * @param prefix
1322     */

1323    public void print(final PrintWriter JavaDoc pw, final String JavaDoc prefix) {
1324        pw.print(prefix);
1325        pw.println("AggStar:");
1326        String JavaDoc subprefix = prefix + " ";
1327
1328        pw.print(subprefix);
1329        pw.print(" bk=");
1330        pw.println(bitKey);
1331
1332        pw.print(subprefix);
1333        pw.print("fbk=");
1334        pw.println(levelBitKey);
1335
1336        pw.print(subprefix);
1337        pw.print("mbk=");
1338        pw.println(measureBitKey);
1339
1340        pw.print(subprefix);
1341        pw.print("has foreign key=");
1342        pw.println(aggTable.hasChildren());
1343
1344        aggTable.print(pw, subprefix);
1345    }
1346}
1347
1348// End AggStar.java
1349
Popular Tags