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