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