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) 2001-2005 Julian Hyde
008// Copyright (C) 2005-2012 Pentaho and others
009// All Rights Reserved.
010//
011// jhyde, 12 August, 2001
012*/
013package mondrian.rolap;
014
015import mondrian.olap.*;
016import mondrian.resource.MondrianResource;
017import mondrian.rolap.agg.*;
018import mondrian.rolap.aggmatcher.AggStar;
019import mondrian.rolap.sql.SqlQuery;
020import mondrian.server.Locus;
021import mondrian.spi.*;
022import mondrian.util.Bug;
023
024import org.apache.commons.collections.map.ReferenceMap;
025import org.apache.log4j.Logger;
026
027import java.io.PrintWriter;
028import java.io.StringWriter;
029import java.lang.ref.SoftReference;
030import java.sql.Connection;
031import java.sql.*;
032import java.util.*;
033
034import javax.sql.DataSource;
035
036/**
037 * A <code>RolapStar</code> is a star schema. It is the means to read cell
038 * values.
039 *
040 * <p>todo: Move this class into a package that specializes in relational
041 * aggregation, doesn't know anything about hierarchies etc.
042 *
043 * @author jhyde
044 * @since 12 August, 2001
045 */
046public class RolapStar {
047    private static final Logger LOGGER = Logger.getLogger(RolapStar.class);
048
049    private final RolapSchema schema;
050
051    // not final for test purposes
052    private DataSource dataSource;
053
054    private final Table factTable;
055
056    /**
057     * Number of columns (column and columnName).
058     */
059    private int columnCount;
060
061    /**
062     * Keeps track of the columns across all tables. Should have
063     * a number of elements equal to columnCount.
064     */
065    private final List<Column> columnList = new ArrayList<Column>();
066
067    private final Dialect sqlQueryDialect;
068
069    /**
070     * If true, then database aggregation information is cached, otherwise
071     * it is flushed after each query.
072     */
073    private boolean cacheAggregations;
074
075    /**
076     * Partially ordered list of AggStars associated with this RolapStar's fact
077     * table.
078     */
079    private final List<AggStar> aggStars = new LinkedList<AggStar>();
080
081    private DataSourceChangeListener changeListener;
082
083    // temporary model, should eventually use RolapStar.Table and
084    // RolapStar.Column
085    private StarNetworkNode factNode;
086    private Map<String, StarNetworkNode> nodeLookup =
087        new HashMap<String, StarNetworkNode>();
088
089    private final RolapStatisticsCache statisticsCache;
090
091    /**
092     * Creates a RolapStar. Please use
093     * {@link RolapSchema.RolapStarRegistry#getOrCreateStar} to create a
094     * {@link RolapStar}.
095     */
096    RolapStar(
097        final RolapSchema schema,
098        final DataSource dataSource,
099        final MondrianDef.Relation fact)
100    {
101        this.cacheAggregations = true;
102        this.schema = schema;
103        this.dataSource = dataSource;
104        this.factTable = new RolapStar.Table(this, fact, null, null);
105
106        // phase out and replace with Table, Column network
107        this.factNode =
108            new StarNetworkNode(null, factTable.alias, null, null, null);
109
110        this.sqlQueryDialect = schema.getDialect();
111        this.changeListener = schema.getDataSourceChangeListener();
112        this.statisticsCache = new RolapStatisticsCache(this);
113    }
114
115    /**
116     * Retrieves the value of the cell identified by a cell request, if it
117     * can be found in the local cache of the current statement (thread).
118     *
119     * <p>If it is not in the local cache, returns null. The client's next
120     * step will presumably be to request a segment that contains the cell
121     * from the global cache, external cache, or by issuing a SQL statement.
122     *
123     * <p>Returns {@link Util#nullValue} if a segment contains the cell and the
124     * cell's value is null.
125     *
126     * <p>If <code>pinSet</code> is not null, pins the segment that holds it
127     * into the local cache. <code>pinSet</code> ensures that a segment is
128     * only pinned once.
129     *
130     * @param request Cell request
131     *
132     * @param pinSet Set into which to pin the segment; or null
133     *
134     * @return Cell value, or {@link Util#nullValue} if the cell value is null,
135     * or null if the cell is not in any segment in the local cache.
136     */
137    public Object getCellFromCache(
138        CellRequest request,
139        RolapAggregationManager.PinSet pinSet)
140    {
141        // REVIEW: Is it possible to optimize this so not every cell lookup
142        // causes an AggregationKey to be created?
143        AggregationKey aggregationKey = new AggregationKey(request);
144
145        final Bar bar = localBars.get();
146        for (SegmentWithData segment : Util.GcIterator.over(bar.segmentRefs)) {
147            if (!segment.getConstrainedColumnsBitKey().equals(
148                    request.getConstrainedColumnsBitKey()))
149            {
150                continue;
151            }
152
153            if (!segment.matches(aggregationKey, request.getMeasure())) {
154                continue;
155            }
156
157            Object o = segment.getCellValue(request.getSingleValues());
158            if (o != null) {
159                if (pinSet != null) {
160                    ((AggregationManager.PinSetImpl) pinSet).add(segment);
161                }
162                return o;
163            }
164        }
165        // No segment contains the requested cell.
166        return null;
167    }
168
169    public Object getCellFromAllCaches(final CellRequest request) {
170        // First, try the local/thread cache.
171        Object result = getCellFromCache(request, null);
172        if (result != null) {
173            return result;
174        }
175        // Now ask the segment cache manager.
176        return getCellFromExternalCache(request);
177    }
178
179    private Object getCellFromExternalCache(CellRequest request) {
180        final SegmentWithData segment =
181            Locus.peek().getServer().getAggregationManager()
182                .cacheMgr.peek(request);
183        if (segment == null) {
184            return null;
185        }
186        return segment.getCellValue(request.getSingleValues());
187    }
188
189    public void register(SegmentWithData segment) {
190        localBars.get().segmentRefs.add(
191            new SoftReference<SegmentWithData>(segment));
192    }
193
194    public RolapStatisticsCache getStatisticsCache() {
195        return statisticsCache;
196    }
197
198    /**
199     * Temporary. Contains the local cache for a particular thread. Because
200     * it is accessed via a thread-local, the data structures can be accessed
201     * without acquiring locks.
202     *
203     * @see Util#deprecated(Object)
204     */
205    public static class Bar {
206        /** Holds all thread-local aggregations of this star. */
207        private final Map<AggregationKey, Aggregation> aggregations =
208            new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK);
209
210        private final List<SoftReference<SegmentWithData>> segmentRefs =
211            new ArrayList<SoftReference<SegmentWithData>>();
212    }
213
214    private final ThreadLocal<Bar> localBars =
215        new ThreadLocal<Bar>() {
216            protected Bar initialValue() {
217                return new Bar();
218            }
219        };
220
221    private static class StarNetworkNode {
222        private StarNetworkNode parent;
223        private MondrianDef.Relation origRel;
224        private String foreignKey;
225        private String joinKey;
226
227        private StarNetworkNode(
228            StarNetworkNode parent,
229            String alias,
230            MondrianDef.Relation origRel,
231            String foreignKey,
232            String joinKey)
233        {
234            this.parent = parent;
235            this.origRel = origRel;
236            this.foreignKey = foreignKey;
237            this.joinKey = joinKey;
238        }
239
240        private boolean isCompatible(
241            StarNetworkNode compatibleParent,
242            MondrianDef.Relation rel,
243            String compatibleForeignKey,
244            String compatibleJoinKey)
245        {
246            return parent == compatibleParent
247                && origRel.getClass().equals(rel.getClass())
248                && foreignKey.equals(compatibleForeignKey)
249                && joinKey.equals(compatibleJoinKey);
250        }
251    }
252
253    protected MondrianDef.RelationOrJoin cloneRelation(
254        MondrianDef.Relation rel,
255        String possibleName)
256    {
257        if (rel instanceof MondrianDef.Table) {
258            MondrianDef.Table tbl = (MondrianDef.Table)rel;
259            return new MondrianDef.Table(
260                tbl,
261                possibleName);
262        } else if (rel instanceof MondrianDef.View) {
263            MondrianDef.View view = (MondrianDef.View)rel;
264            MondrianDef.View newView = new MondrianDef.View(view);
265            newView.alias = possibleName;
266            return newView;
267        } else if (rel instanceof MondrianDef.InlineTable) {
268            MondrianDef.InlineTable inlineTable =
269                (MondrianDef.InlineTable) rel;
270            MondrianDef.InlineTable newInlineTable =
271                new MondrianDef.InlineTable(inlineTable);
272            newInlineTable.alias = possibleName;
273            return newInlineTable;
274        } else {
275            throw new UnsupportedOperationException();
276        }
277    }
278
279    /**
280     * Generates a unique relational join to the fact table via re-aliasing
281     * MondrianDef.Relations
282     *
283     * currently called in the RolapCubeHierarchy constructor.  This should
284     * eventually be phased out and replaced with RolapStar.Table and
285     * RolapStar.Column references
286     *
287     * @param rel the relation needing uniqueness
288     * @param factForeignKey the foreign key of the fact table
289     * @param primaryKey the join key of the relation
290     * @param primaryKeyTable the join table of the relation
291     * @return if necessary a new relation that has been re-aliased
292     */
293    public MondrianDef.RelationOrJoin getUniqueRelation(
294        MondrianDef.RelationOrJoin rel,
295        String factForeignKey,
296        String primaryKey,
297        String primaryKeyTable)
298    {
299        return getUniqueRelation(
300            factNode, rel, factForeignKey, primaryKey, primaryKeyTable);
301    }
302
303    private MondrianDef.RelationOrJoin getUniqueRelation(
304        StarNetworkNode parent,
305        MondrianDef.RelationOrJoin relOrJoin,
306        String foreignKey,
307        String joinKey,
308        String joinKeyTable)
309    {
310        if (relOrJoin == null) {
311            return null;
312        } else if (relOrJoin instanceof MondrianDef.Relation) {
313            int val = 0;
314            MondrianDef.Relation rel =
315                (MondrianDef.Relation) relOrJoin;
316            String newAlias =
317                joinKeyTable != null ? joinKeyTable : rel.getAlias();
318            while (true) {
319                StarNetworkNode node = nodeLookup.get(newAlias);
320                if (node == null) {
321                    if (val != 0) {
322                        rel = (MondrianDef.Relation)
323                            cloneRelation(rel, newAlias);
324                    }
325                    node =
326                        new StarNetworkNode(
327                            parent, newAlias, rel, foreignKey, joinKey);
328                    nodeLookup.put(newAlias, node);
329                    return rel;
330                } else if (node.isCompatible(
331                        parent, rel, foreignKey, joinKey))
332                {
333                    return node.origRel;
334                }
335                newAlias = rel.getAlias() + "_" + (++val);
336            }
337        } else if (relOrJoin instanceof MondrianDef.Join) {
338            // determine if the join starts from the left or right side
339            MondrianDef.Join join = (MondrianDef.Join)relOrJoin;
340            if (join.left instanceof MondrianDef.Join) {
341                throw MondrianResource.instance().IllegalLeftDeepJoin.ex();
342            }
343            final MondrianDef.RelationOrJoin left;
344            final MondrianDef.RelationOrJoin right;
345            if (join.getLeftAlias().equals(joinKeyTable)) {
346                // first manage left then right
347                left =
348                    getUniqueRelation(
349                        parent, join.left, foreignKey,
350                        joinKey, joinKeyTable);
351                parent = nodeLookup.get(
352                    ((MondrianDef.Relation) left).getAlias());
353                right =
354                    getUniqueRelation(
355                        parent, join.right, join.leftKey,
356                        join.rightKey, join.getRightAlias());
357            } else if (join.getRightAlias().equals(joinKeyTable)) {
358                // right side must equal
359                right =
360                    getUniqueRelation(
361                        parent, join.right, foreignKey,
362                        joinKey, joinKeyTable);
363                parent = nodeLookup.get(
364                    ((MondrianDef.Relation) right).getAlias());
365                left =
366                    getUniqueRelation(
367                        parent, join.left, join.rightKey,
368                        join.leftKey, join.getLeftAlias());
369            } else {
370                throw new MondrianException(
371                    "failed to match primary key table to join tables");
372            }
373
374            if (join.left != left || join.right != right) {
375                join =
376                    new MondrianDef.Join(
377                        left instanceof MondrianDef.Relation
378                            ? ((MondrianDef.Relation) left).getAlias()
379                            : null,
380                        join.leftKey,
381                        left,
382                        right instanceof MondrianDef.Relation
383                            ? ((MondrianDef.Relation) right).getAlias()
384                            : null,
385                        join.rightKey,
386                        right);
387            }
388            return join;
389        }
390        return null;
391    }
392
393    /**
394     * Returns this RolapStar's column count. After a star has been created with
395     * all of its columns, this is the number of columns in the star.
396     */
397    public int getColumnCount() {
398        return columnCount;
399    }
400
401    /**
402     * This is used by the {@link Column} constructor to get a unique id (per
403     * its parent {@link RolapStar}).
404     */
405    private int nextColumnCount() {
406        return columnCount++;
407    }
408
409    /**
410     * Decrements the column counter; used if a newly
411     * created column is found to already exist.
412     */
413    private int decrementColumnCount() {
414        return columnCount--;
415    }
416
417    /**
418     * Place holder in case in the future we wish to be able to
419     * reload aggregates. In that case, if aggregates had already been loaded,
420     * i.e., this star has some aggstars, then those aggstars are cleared.
421     */
422    public void prepareToLoadAggregates() {
423        aggStars.clear();
424    }
425
426    /**
427     * Adds an {@link AggStar} to this star.
428     *
429     * <p>Internally the AggStars are added in sort order, smallest row count
430     * to biggest, so that the most efficient AggStar is encountered first;
431     * ties do not matter.
432     */
433    public void addAggStar(AggStar aggStar) {
434        // Add it before the first AggStar which is larger, if there is one.
435        int size = aggStar.getSize();
436        ListIterator<AggStar> lit = aggStars.listIterator();
437        while (lit.hasNext()) {
438            AggStar as = lit.next();
439            if (as.getSize() >= size) {
440                lit.previous();
441                lit.add(aggStar);
442                return;
443            }
444        }
445
446        // There is no larger star. Add at the end of the list.
447        aggStars.add(aggStar);
448    }
449
450    /**
451     * Clears the list of agg stars.
452     */
453    void clearAggStarList() {
454        aggStars.clear();
455    }
456
457    /**
458     * Reorder the list of aggregate stars. This should be called if the
459     * algorithm used to order the AggStars has been changed.
460     */
461    public void reOrderAggStarList() {
462        List<AggStar> oldList = new ArrayList<AggStar>(aggStars);
463        aggStars.clear();
464        for (AggStar aggStar : oldList) {
465            addAggStar(aggStar);
466        }
467    }
468
469    /**
470     * Returns this RolapStar's aggregate table AggStars, ordered in ascending
471     * order of size.
472     */
473    public List<AggStar> getAggStars() {
474        return aggStars;
475    }
476
477    /**
478     * Returns the fact table at the center of this RolapStar.
479     *
480     * @return fact table
481     */
482    public Table getFactTable() {
483        return factTable;
484    }
485
486    /**
487     * Clones an existing SqlQuery to create a new one (this cloning creates one
488     * with an empty sql query).
489     */
490    public SqlQuery getSqlQuery() {
491        return new SqlQuery(getSqlQueryDialect());
492    }
493
494    /**
495     * Returns this RolapStar's SQL dialect.
496     */
497    public Dialect getSqlQueryDialect() {
498        return sqlQueryDialect;
499    }
500
501    /**
502     * Sets whether to cache database aggregation information; if false, cache
503     * is flushed after each query.
504     *
505     * <p>This method is called only by the RolapCube and is only called if
506     * caching is to be turned off. Note that the same RolapStar can be
507     * associated with more than on RolapCube. If any one of those cubes has
508     * caching turned off, then caching is turned off for all of them.
509     *
510     * @param cacheAggregations Whether to cache database aggregation
511     */
512    void setCacheAggregations(boolean cacheAggregations) {
513        // this can only change from true to false
514        this.cacheAggregations = cacheAggregations;
515        clearCachedAggregations(false);
516    }
517
518    /**
519     * Returns whether the this RolapStar cache aggregates.
520     *
521     * @see #setCacheAggregations(boolean)
522     */
523    boolean isCacheAggregations() {
524        return this.cacheAggregations;
525    }
526
527    boolean isCacheDisabled() {
528        return MondrianProperties.instance().DisableCaching.get();
529    }
530
531    /**
532     * Clears the aggregate cache. This only does something if aggregate caching
533     * is disabled (see {@link #setCacheAggregations(boolean)}).
534     *
535     * @param forced If true, clears cached aggregations regardless of any other
536     *   settings.  If false, clears only cache from the current thread
537     */
538    void clearCachedAggregations(boolean forced) {
539        if (forced || !cacheAggregations || isCacheDisabled()) {
540            if (LOGGER.isDebugEnabled()) {
541                StringBuilder buf = new StringBuilder(100);
542                buf.append("RolapStar.clearCachedAggregations: schema=");
543                buf.append(schema.getName());
544                buf.append(", star=");
545                buf.append(getFactTable().getAlias());
546                LOGGER.debug(buf.toString());
547            }
548
549            // Clear aggregation cache for the current thread context.
550            localBars.get().aggregations.clear();
551            localBars.get().segmentRefs.clear();
552        }
553    }
554
555    /**
556     * Looks up an aggregation or creates one if it does not exist in an
557     * atomic (synchronized) operation.
558     *
559     * <p>When a new aggregation is created, it is marked as thread local.
560     *
561     * @param aggregationKey this is the constrained column bitkey
562     */
563    public Aggregation lookupOrCreateAggregation(
564        AggregationKey aggregationKey)
565    {
566        Aggregation aggregation = lookupSegment(aggregationKey);
567        if (aggregation != null) {
568            return aggregation;
569        }
570
571        aggregation =
572            new Aggregation(
573                aggregationKey);
574
575        localBars.get().aggregations.put(
576            aggregationKey, aggregation);
577
578        // Let the change listener get the opportunity to register the
579        // first time the aggregation is used
580        if (this.cacheAggregations
581            && !isCacheDisabled()
582            && changeListener != null)
583        {
584            Util.discard(
585                changeListener.isAggregationChanged(aggregationKey));
586        }
587        return aggregation;
588    }
589
590    /**
591     * Looks for an existing aggregation over a given set of columns, in the
592     * local segment cache, returning <code>null</code> if there is none.
593     *
594     * <p>Must be called from synchronized context.
595     *
596     * @see Util#deprecated(Object)  currently always returns null -- remove
597     */
598    public Aggregation lookupSegment(AggregationKey aggregationKey) {
599        return localBars.get().aggregations.get(aggregationKey);
600    }
601
602    /** For testing purposes only.  */
603    public void setDataSource(DataSource dataSource) {
604        this.dataSource = dataSource;
605    }
606
607    /**
608     * Returns the DataSource used to connect to the underlying DBMS.
609     *
610     * @return DataSource
611     */
612    public DataSource getDataSource() {
613        return dataSource;
614    }
615
616    /**
617     * Retrieves the {@link RolapStar.Measure} in which a measure is stored.
618     */
619    public static Measure getStarMeasure(Member member) {
620        return (Measure) ((RolapStoredMeasure) member).getStarMeasure();
621    }
622
623    /**
624     * Retrieves a named column, returns null if not found.
625     */
626    public Column[] lookupColumns(String tableAlias, String columnName) {
627        final Table table = factTable.findDescendant(tableAlias);
628        return (table == null) ? null : table.lookupColumns(columnName);
629    }
630
631    /**
632     * This is used by TestAggregationManager only.
633     */
634    public Column lookupColumn(String tableAlias, String columnName) {
635        final Table table = factTable.findDescendant(tableAlias);
636        return (table == null) ? null : table.lookupColumn(columnName);
637    }
638
639    public BitKey getBitKey(String[] tableAlias, String[] columnName) {
640        BitKey bitKey = BitKey.Factory.makeBitKey(getColumnCount());
641        Column starColumn;
642        for (int i = 0; i < tableAlias.length; i ++) {
643            starColumn = lookupColumn(tableAlias[i], columnName[i]);
644            if (starColumn != null) {
645                bitKey.set(starColumn.getBitPosition());
646            }
647        }
648        return bitKey;
649    }
650
651    /**
652     * Returns a list of all aliases used in this star.
653     */
654    public List<String> getAliasList() {
655        List<String> aliasList = new ArrayList<String>();
656        if (factTable != null) {
657            collectAliases(aliasList, factTable);
658        }
659        return aliasList;
660    }
661
662    /**
663     * Finds all of the table aliases in a table and its children.
664     */
665    private static void collectAliases(List<String> aliasList, Table table) {
666        aliasList.add(table.getAlias());
667        for (Table child : table.children) {
668            collectAliases(aliasList, child);
669        }
670    }
671
672    /**
673     * Collects all columns in this table and its children.
674     * If <code>joinColumn</code> is specified, only considers child tables
675     * joined by the given column.
676     */
677    public static void collectColumns(
678        Collection<Column> columnList,
679        Table table,
680        MondrianDef.Column joinColumn)
681    {
682        if (joinColumn == null) {
683            columnList.addAll(table.columnList);
684        }
685        for (Table child : table.children) {
686            if (joinColumn == null
687                || child.getJoinCondition().left.equals(joinColumn))
688            {
689                collectColumns(columnList, child, null);
690            }
691        }
692    }
693
694    private boolean containsColumn(String tableName, String columnName) {
695        Connection jdbcConnection;
696        try {
697            jdbcConnection = dataSource.getConnection();
698        } catch (SQLException e1) {
699            throw Util.newInternal(
700                e1, "Error while creating connection from data source");
701        }
702        try {
703            final DatabaseMetaData metaData = jdbcConnection.getMetaData();
704            final ResultSet columns =
705                metaData.getColumns(null, null, tableName, columnName);
706            return columns.next();
707        } catch (SQLException e) {
708            throw Util.newInternal(
709                "Error while retrieving metadata for table '" + tableName
710                + "', column '" + columnName + "'");
711        } finally {
712            try {
713                jdbcConnection.close();
714            } catch (SQLException e) {
715                // ignore
716            }
717        }
718    }
719
720    /**
721     * Adds a column to the star's list of all columns across all tables.
722     *
723     * @param c the column to add
724     */
725    private void addColumn(Column c) {
726        columnList.add(c.getBitPosition(), c);
727    }
728
729    /**
730     * Look up the column at the given bit position.
731     *
732     * @param bitPos bit position to look up
733     * @return column at the given position
734     */
735    public Column getColumn(int bitPos) {
736        return columnList.get(bitPos);
737    }
738
739    public RolapSchema getSchema() {
740        return schema;
741    }
742
743    /**
744     * Generates a SQL statement to read all instances of the given attributes.
745     *
746     * <p>The SQL statement is of the form {@code SELECT ... FROM ... JOIN ...
747     * GROUP BY ...}. It is useful for populating an aggregate table.
748     *
749     * @param columnList List of columns (attributes and measures)
750     * @param columnNameList List of column names (must have same cardinality
751     *     as {@code columnList})
752     * @return SQL SELECT statement
753     */
754    public String generateSql(
755        List<Column> columnList,
756        List<String> columnNameList)
757    {
758        final SqlQuery query = new SqlQuery(sqlQueryDialect, true);
759        query.addFrom(
760            factTable.relation,
761            factTable.relation.getAlias(),
762            false);
763        int k = -1;
764        for (Column column : columnList) {
765            ++k;
766            column.table.addToFrom(query,  false, true);
767            String columnExpr = column.generateExprString(query);
768            if (column instanceof Measure) {
769                Measure measure = (Measure) column;
770                columnExpr = measure.getAggregator().getExpression(columnExpr);
771            }
772            final String columnName = columnNameList.get(k);
773            String alias = query.addSelect(columnExpr, null, columnName);
774            if (!(column instanceof Measure)) {
775                query.addGroupBy(columnExpr, alias);
776            }
777        }
778        // remove whitespace from query - in particular, the trailing newline
779        return query.toString().trim();
780    }
781
782    public String toString() {
783        StringWriter sw = new StringWriter(256);
784        PrintWriter pw = new PrintWriter(sw);
785        print(pw, "", true);
786        pw.flush();
787        return sw.toString();
788    }
789
790    /**
791     * Prints the state of this <code>RolapStar</code>
792     *
793     * @param pw Writer
794     * @param prefix Prefix to print at the start of each line
795     * @param structure Whether to print the structure of the star
796     */
797    public void print(PrintWriter pw, String prefix, boolean structure) {
798        if (structure) {
799            pw.print(prefix);
800            pw.println("RolapStar:");
801            String subprefix = prefix + "  ";
802            factTable.print(pw, subprefix);
803
804            for (AggStar aggStar : getAggStars()) {
805                aggStar.print(pw, subprefix);
806            }
807        }
808    }
809
810    /**
811     * Returns the listener for changes to this star's underlying database.
812     *
813     * @return Returns the Data source change listener.
814     */
815    public DataSourceChangeListener getChangeListener() {
816        return changeListener;
817    }
818
819    /**
820     * Sets the listener for changes to this star's underlying database.
821     *
822     * @param changeListener The Data source change listener to set
823     */
824    public void setChangeListener(DataSourceChangeListener changeListener) {
825        this.changeListener = changeListener;
826    }
827
828    // -- Inner classes --------------------------------------------------------
829
830    /**
831     * A column in a star schema.
832     */
833    public static class Column {
834        public static final Comparator<Column> COMPARATOR =
835            new Comparator<Column>() {
836                public int compare(
837                    Column object1,
838                    Column object2)
839                {
840                    return Util.compare(
841                        object1.getBitPosition(),
842                        object2.getBitPosition());
843                }
844        };
845
846        private final Table table;
847        private final MondrianDef.Expression expression;
848        private final Dialect.Datatype datatype;
849        private final SqlStatement.Type internalType;
850        private final String name;
851
852        /**
853         * When a Column is a column, and not a Measure, the parent column
854         * is the coloumn associated with next highest Level.
855         */
856        private final Column parentColumn;
857
858        /**
859         * This is used during both aggregate table recognition and aggregate
860         * table generation. For multiple dimension usages, multiple shared
861         * dimension or unshared dimension with the same column names,
862         * this is used to disambiguate aggregate column names.
863         */
864        private final String usagePrefix;
865        /**
866         * This is only used in RolapAggregationManager and adds
867         * non-constraining columns making the drill-through queries easier for
868         * humans to understand.
869         */
870        private final Column nameColumn;
871
872        private boolean isNameColumn;
873
874        /** this has a unique value per star */
875        private final int bitPosition;
876        /**
877         * The estimated cardinality of the column.
878         * {@link Integer#MIN_VALUE} means unknown.
879         */
880        private int approxCardinality = Integer.MIN_VALUE;
881
882        private Column(
883            String name,
884            Table table,
885            MondrianDef.Expression expression,
886            Dialect.Datatype datatype)
887        {
888            this(
889                name, table, expression, datatype, null, null,
890                null, null, Integer.MIN_VALUE, table.star.nextColumnCount());
891        }
892
893        private Column(
894            String name,
895            Table table,
896            MondrianDef.Expression expression,
897            Dialect.Datatype datatype,
898            SqlStatement.Type internalType,
899            Column nameColumn,
900            Column parentColumn,
901            String usagePrefix,
902            int approxCardinality,
903            int bitPosition)
904        {
905            this.name = name;
906            this.table = table;
907            this.expression = expression;
908            assert expression == null
909                || expression.getGenericExpression() != null;
910            this.datatype = datatype;
911            this.internalType = internalType;
912            this.bitPosition = bitPosition;
913            this.nameColumn = nameColumn;
914            this.parentColumn = parentColumn;
915            this.usagePrefix = usagePrefix;
916            this.approxCardinality = approxCardinality;
917            if (nameColumn != null) {
918                nameColumn.isNameColumn = true;
919            }
920            if (table != null) {
921                table.star.addColumn(this);
922            }
923        }
924
925        /**
926         * Fake column.
927         *
928         * @param datatype Datatype
929         */
930        protected Column(Dialect.Datatype datatype)
931        {
932            this(
933                null,
934                null,
935                null,
936                datatype,
937                null,
938                null,
939                null,
940                null,
941                Integer.MIN_VALUE,
942                0);
943        }
944
945        public boolean equals(Object obj) {
946            if (! (obj instanceof RolapStar.Column)) {
947                return false;
948            }
949            RolapStar.Column other = (RolapStar.Column) obj;
950            // Note: both columns have to be from the same table
951            return
952                other.table == this.table
953                && Util.equals(other.expression, this.expression)
954                && other.datatype == this.datatype
955                && other.name.equals(this.name);
956        }
957
958        public int hashCode() {
959            int h = name.hashCode();
960            h = Util.hash(h, table);
961            return h;
962        }
963
964        public String getName() {
965            return name;
966        }
967
968        public int getBitPosition() {
969            return bitPosition;
970        }
971
972        public RolapStar getStar() {
973            return table.star;
974        }
975
976        public RolapStar.Table getTable() {
977            return table;
978        }
979
980        public SqlQuery getSqlQuery() {
981            return getTable().getStar().getSqlQuery();
982        }
983
984        public RolapStar.Column getNameColumn() {
985            return nameColumn;
986        }
987
988        public RolapStar.Column getParentColumn() {
989            return parentColumn;
990        }
991
992        public String getUsagePrefix() {
993            return usagePrefix;
994        }
995
996        public boolean isNameColumn() {
997            return isNameColumn;
998        }
999
1000        public MondrianDef.Expression getExpression() {
1001            return expression;
1002        }
1003
1004        /**
1005         * Generates a SQL expression, which typically this looks like
1006         * this: <code><i>tableName</i>.<i>columnName</i></code>.
1007         */
1008        public String generateExprString(SqlQuery query) {
1009            return getExpression().getExpression(query);
1010        }
1011
1012        /**
1013         * Get column cardinality from the schema cache if possible;
1014         * otherwise issue a select count(distinct) query to retrieve
1015         * the cardinality and stores it in the cache.
1016         *
1017         * @return the column cardinality.
1018         */
1019        public int getCardinality() {
1020            if (approxCardinality < 0) {
1021                approxCardinality =
1022                    table.star.getStatisticsCache().getColumnCardinality(
1023                        table.relation, expression, approxCardinality);
1024            }
1025            return approxCardinality;
1026        }
1027
1028        /**
1029         * Generates a predicate that a column matches one of a list of values.
1030         *
1031         * <p>
1032         * Several possible outputs, depending upon whether the there are
1033         * nulls:<ul>
1034         *
1035         * <li>One not-null value: <code>foo.bar = 1</code>
1036         *
1037         * <li>All values not null: <code>foo.bar in (1, 2, 3)</code></li
1038         *
1039         * <li>Null and not null values:
1040         * <code>(foo.bar is null or foo.bar in (1, 2))</code></li>
1041         *
1042         * <li>Only null values:
1043         * <code>foo.bar is null</code></li>
1044         *
1045         * <li>String values: <code>foo.bar in ('a', 'b', 'c')</code></li>
1046         *
1047         * </ul>
1048         */
1049        public static String createInExpr(
1050            final String expr,
1051            StarColumnPredicate predicate,
1052            Dialect.Datatype datatype,
1053            SqlQuery sqlQuery)
1054        {
1055            // Sometimes a column predicate is created without a column. This
1056            // is unfortunate, and we will fix it some day. For now, create
1057            // a fake column with all of the information needed by the toSql
1058            // method, and a copy of the predicate wrapping that fake column.
1059            if (!Bug.BugMondrian313Fixed
1060                || !Bug.BugMondrian314Fixed
1061                && predicate.getConstrainedColumn() == null)
1062            {
1063                Column column = new Column(datatype) {
1064                    public String generateExprString(SqlQuery query) {
1065                        return expr;
1066                    }
1067                };
1068                predicate = predicate.cloneWithColumn(column);
1069            }
1070
1071            StringBuilder buf = new StringBuilder(64);
1072            predicate.toSql(sqlQuery, buf);
1073            return buf.toString();
1074        }
1075
1076        public String toString() {
1077            StringWriter sw = new StringWriter(256);
1078            PrintWriter pw = new PrintWriter(sw);
1079            print(pw, "");
1080            pw.flush();
1081            return sw.toString();
1082        }
1083
1084        /**
1085         * Prints this column.
1086         *
1087         * @param pw Print writer
1088         * @param prefix Prefix to print first, such as spaces for indentation
1089         */
1090        public void print(PrintWriter pw, String prefix) {
1091            SqlQuery sqlQuery = getSqlQuery();
1092            pw.print(prefix);
1093            pw.print(getName());
1094            pw.print(" (");
1095            pw.print(getBitPosition());
1096            pw.print("): ");
1097            pw.print(generateExprString(sqlQuery));
1098        }
1099
1100        public Dialect.Datatype getDatatype() {
1101            return datatype;
1102        }
1103
1104        /**
1105         * Returns a string representation of the datatype of this column, in
1106         * the dialect specified. For example, 'DECIMAL(10, 2) NOT NULL'.
1107         *
1108         * @param dialect Dialect
1109         * @return String representation of column's datatype
1110         */
1111        public String getDatatypeString(Dialect dialect) {
1112            final SqlQuery query = new SqlQuery(dialect);
1113            query.addFrom(
1114                table.star.factTable.relation, table.star.factTable.alias,
1115                false);
1116            query.addFrom(table.relation, table.alias, false);
1117            query.addSelect(expression.getExpression(query), null);
1118            final String sql = query.toString();
1119            Connection jdbcConnection = null;
1120            try {
1121                jdbcConnection = table.star.dataSource.getConnection();
1122                final PreparedStatement pstmt =
1123                    jdbcConnection.prepareStatement(sql);
1124                final ResultSetMetaData resultSetMetaData =
1125                    pstmt.getMetaData();
1126                assert resultSetMetaData.getColumnCount() == 1;
1127                final String type = resultSetMetaData.getColumnTypeName(1);
1128                int precision = resultSetMetaData.getPrecision(1);
1129                final int scale = resultSetMetaData.getScale(1);
1130                if (type.equals("DOUBLE")) {
1131                    precision = 0;
1132                }
1133                String typeString;
1134                if (precision == 0) {
1135                    typeString = type;
1136                } else if (scale == 0) {
1137                    typeString = type + "(" + precision + ")";
1138                } else {
1139                    typeString = type + "(" + precision + ", " + scale + ")";
1140                }
1141                pstmt.close();
1142                jdbcConnection.close();
1143                jdbcConnection = null;
1144                return typeString;
1145            } catch (SQLException e) {
1146                throw Util.newError(
1147                    e,
1148                    "Error while deriving type of column " + toString());
1149            } finally {
1150                if (jdbcConnection != null) {
1151                    try {
1152                        jdbcConnection.close();
1153                    } catch (SQLException e) {
1154                        // ignore
1155                    }
1156                }
1157            }
1158        }
1159
1160        public SqlStatement.Type getInternalType() {
1161            return internalType;
1162        }
1163    }
1164
1165    /**
1166     * Definition of a measure in a star schema.
1167     *
1168     * <p>A measure is basically just a column; except that its
1169     * {@link #aggregator} defines how it is to be rolled up.
1170     */
1171    public static class Measure extends Column {
1172        private final String cubeName;
1173        private final RolapAggregator aggregator;
1174
1175        public Measure(
1176            String name,
1177            String cubeName,
1178            RolapAggregator aggregator,
1179            Table table,
1180            MondrianDef.Expression expression,
1181            Dialect.Datatype datatype)
1182        {
1183            super(name, table, expression, datatype);
1184            this.cubeName = cubeName;
1185            this.aggregator = aggregator;
1186        }
1187
1188        public RolapAggregator getAggregator() {
1189            return aggregator;
1190        }
1191
1192        public boolean equals(Object o) {
1193            if (! (o instanceof RolapStar.Measure)) {
1194                return false;
1195            }
1196            RolapStar.Measure that = (RolapStar.Measure) o;
1197            if (!super.equals(that)) {
1198                return false;
1199            }
1200            // Measure names are only unique within their cube - and remember
1201            // that a given RolapStar can support multiple cubes if they have
1202            // the same fact table.
1203            if (!cubeName.equals(that.cubeName)) {
1204                return false;
1205            }
1206            // Note: both measure have to have the same aggregator
1207            return (that.aggregator == this.aggregator);
1208        }
1209
1210        public int hashCode() {
1211            int h = super.hashCode();
1212            h = Util.hash(h, aggregator);
1213            return h;
1214        }
1215
1216        public void print(PrintWriter pw, String prefix) {
1217            SqlQuery sqlQuery = getSqlQuery();
1218            pw.print(prefix);
1219            pw.print(getName());
1220            pw.print(" (");
1221            pw.print(getBitPosition());
1222            pw.print("): ");
1223            pw.print(
1224                aggregator.getExpression(
1225                    getExpression() == null
1226                        ? null
1227                        : generateExprString(sqlQuery)));
1228        }
1229
1230        public String getCubeName() {
1231            return cubeName;
1232        }
1233    }
1234
1235    /**
1236     * Definition of a table in a star schema.
1237     *
1238     * <p>A 'table' is defined by a
1239     * {@link mondrian.olap.MondrianDef.RelationOrJoin} so may, in fact, be a
1240     * view.
1241     *
1242     * <p>Every table in the star schema except the fact table has a parent
1243     * table, and a condition which specifies how it is joined to its parent.
1244     * So the star schema is, in effect, a hierarchy with the fact table at
1245     * its root.
1246     */
1247    public static class Table {
1248        private final RolapStar star;
1249        private final MondrianDef.Relation relation;
1250        private final List<Column> columnList;
1251        private final Table parent;
1252        private List<Table> children;
1253        private final Condition joinCondition;
1254        private final String alias;
1255
1256        private Table(
1257            RolapStar star,
1258            MondrianDef.Relation relation,
1259            Table parent,
1260            Condition joinCondition)
1261        {
1262            this.star = star;
1263            this.relation = relation;
1264            this.alias = chooseAlias();
1265            this.parent = parent;
1266            final AliasReplacer aliasReplacer =
1267                    new AliasReplacer(relation.getAlias(), this.alias);
1268            this.joinCondition = aliasReplacer.visit(joinCondition);
1269            if (this.joinCondition != null) {
1270                this.joinCondition.table = this;
1271            }
1272            this.columnList = new ArrayList<Column>();
1273            this.children = Collections.emptyList();
1274            Util.assertTrue((parent == null) == (joinCondition == null));
1275        }
1276
1277        /**
1278         * Returns the condition by which a dimension table is connected to its
1279         * {@link #getParentTable() parent}; or null if this is the fact table.
1280         */
1281        public Condition getJoinCondition() {
1282            return joinCondition;
1283        }
1284
1285        /**
1286         * Returns this table's parent table, or null if this is the fact table
1287         * (which is at the center of the star).
1288         */
1289        public Table getParentTable() {
1290            return parent;
1291        }
1292
1293        private void addColumn(Column column) {
1294            columnList.add(column);
1295        }
1296
1297        /**
1298         * Adds to a list all columns of this table or a child table
1299         * which are present in a given bitKey.
1300         *
1301         * <p>Note: This method is slow, but that's acceptable because it is
1302         * only used for tracing. It would be more efficient to store an
1303         * array in the {@link RolapStar} mapping column ordinals to columns.
1304         */
1305        private void collectColumns(BitKey bitKey, List<Column> list) {
1306            for (Column column : getColumns()) {
1307                if (bitKey.get(column.getBitPosition())) {
1308                    list.add(column);
1309                }
1310            }
1311            for (Table table : getChildren()) {
1312                table.collectColumns(bitKey, list);
1313            }
1314        }
1315
1316        /**
1317         * Returns an array of all columns in this star with a given name.
1318         */
1319        public Column[] lookupColumns(String columnName) {
1320            List<Column> l = new ArrayList<Column>();
1321            for (Column column : getColumns()) {
1322                if (column.getExpression() instanceof MondrianDef.Column) {
1323                    MondrianDef.Column columnExpr =
1324                        (MondrianDef.Column) column.getExpression();
1325                    if (columnExpr.name.equals(columnName)) {
1326                        l.add(column);
1327                    }
1328                } else if (column.getExpression()
1329                        instanceof MondrianDef.KeyExpression)
1330                {
1331                    MondrianDef.KeyExpression columnExpr =
1332                        (MondrianDef.KeyExpression) column.getExpression();
1333                    if (columnExpr.toString().equals(columnName)) {
1334                        l.add(column);
1335                    }
1336                }
1337            }
1338            return l.toArray(new Column[l.size()]);
1339        }
1340
1341        public Column lookupColumn(String columnName) {
1342            for (Column column : getColumns()) {
1343                if (column.getExpression() instanceof MondrianDef.Column) {
1344                    MondrianDef.Column columnExpr =
1345                        (MondrianDef.Column) column.getExpression();
1346                    if (columnExpr.name.equals(columnName)) {
1347                        return column;
1348                    }
1349                } else if (column.getExpression()
1350                        instanceof MondrianDef.KeyExpression)
1351                {
1352                    MondrianDef.KeyExpression columnExpr =
1353                        (MondrianDef.KeyExpression) column.getExpression();
1354                    if (columnExpr.toString().equals(columnName)) {
1355                        return column;
1356                    }
1357                } else if (column.getName().equals(columnName)) {
1358                    return column;
1359                }
1360            }
1361            return null;
1362        }
1363
1364        /**
1365         * Given a MondrianDef.Expression return a column with that expression
1366         * or null.
1367         */
1368        public Column lookupColumnByExpression(MondrianDef.Expression xmlExpr) {
1369            for (Column column : getColumns()) {
1370                if (column instanceof Measure) {
1371                    continue;
1372                }
1373                if (column.getExpression().equals(xmlExpr)) {
1374                    return column;
1375                }
1376            }
1377            return null;
1378        }
1379
1380        public boolean containsColumn(Column column) {
1381            return getColumns().contains(column);
1382        }
1383
1384        /**
1385         * Look up a {@link Measure} by its name.
1386         * Returns null if not found.
1387         */
1388        public Measure lookupMeasureByName(String cubeName, String name) {
1389            for (Column column : getColumns()) {
1390                if (column instanceof Measure) {
1391                    Measure measure = (Measure) column;
1392                    if (measure.getName().equals(name)
1393                        && measure.getCubeName().equals(cubeName))
1394                    {
1395                        return measure;
1396                    }
1397                }
1398            }
1399            return null;
1400        }
1401
1402        RolapStar getStar() {
1403            return star;
1404        }
1405        private SqlQuery getSqlQuery() {
1406            return getStar().getSqlQuery();
1407        }
1408        public MondrianDef.Relation getRelation() {
1409            return relation;
1410        }
1411
1412        /** Chooses an alias which is unique within the star. */
1413        private String chooseAlias() {
1414            List<String> aliasList = star.getAliasList();
1415            for (int i = 0;; ++i) {
1416                String candidateAlias = relation.getAlias();
1417                if (i > 0) {
1418                    candidateAlias += "_" + i;
1419                }
1420                if (!aliasList.contains(candidateAlias)) {
1421                    return candidateAlias;
1422                }
1423            }
1424        }
1425
1426        public String getAlias() {
1427            return alias;
1428        }
1429
1430        /**
1431         * Sometimes one need to get to the "real" name when the table has
1432         * been given an alias.
1433         */
1434        public String getTableName() {
1435            if (relation instanceof MondrianDef.Table) {
1436                MondrianDef.Table t = (MondrianDef.Table) relation;
1437                return t.name;
1438            } else {
1439                return null;
1440            }
1441        }
1442
1443        synchronized void makeMeasure(RolapBaseCubeMeasure measure) {
1444            // Remove assertion to allow cube to be recreated
1445            // assert lookupMeasureByName(
1446            //    measure.getCube().getName(), measure.getName()) == null;
1447            RolapStar.Measure starMeasure = new RolapStar.Measure(
1448                measure.getName(),
1449                measure.getCube().getName(),
1450                measure.getAggregator(),
1451                this,
1452                measure.getMondrianDefExpression(),
1453                measure.getDatatype());
1454
1455            measure.setStarMeasure(starMeasure); // reverse mapping
1456
1457            if (containsColumn(starMeasure)) {
1458                star.decrementColumnCount();
1459            } else {
1460                addColumn(starMeasure);
1461            }
1462        }
1463
1464        /**
1465         * This is only called by RolapCube. If the RolapLevel has a non-null
1466         * name expression then two columns will be made, otherwise only one.
1467         * Updates the RolapLevel to RolapStar.Column mapping associated with
1468         * this cube.
1469         *
1470         * @param cube Cube
1471         * @param level Level
1472         * @param parentColumn Parent column
1473         */
1474        synchronized Column makeColumns(
1475            RolapCube cube,
1476            RolapCubeLevel level,
1477            Column parentColumn,
1478            String usagePrefix)
1479        {
1480            Column nameColumn = null;
1481            if (level.getNameExp() != null) {
1482                // make a column for the name expression
1483                nameColumn = makeColumnForLevelExpr(
1484                    cube,
1485                    level,
1486                    level.getName(),
1487                    level.getNameExp(),
1488                    Dialect.Datatype.String,
1489                    null,
1490                    null,
1491                    null,
1492                    null);
1493            }
1494
1495            // select the column's name depending upon whether or not a
1496            // "named" column, above, has been created.
1497            String name = (level.getNameExp() == null)
1498                ? level.getName()
1499                : level.getName() + " (Key)";
1500
1501            // If the nameColumn is not null, then it is associated with this
1502            // column.
1503            Column column = makeColumnForLevelExpr(
1504                cube,
1505                level,
1506                name,
1507                level.getKeyExp(),
1508                level.getDatatype(),
1509                level.getInternalType(),
1510                nameColumn,
1511                parentColumn,
1512                usagePrefix);
1513
1514            if (column != null) {
1515                level.setStarKeyColumn(column);
1516            }
1517
1518            return column;
1519        }
1520
1521        private Column makeColumnForLevelExpr(
1522            RolapCube cube,
1523            RolapLevel level,
1524            String name,
1525            MondrianDef.Expression xmlExpr,
1526            Dialect.Datatype datatype,
1527            SqlStatement.Type internalType,
1528            Column nameColumn,
1529            Column parentColumn,
1530            String usagePrefix)
1531        {
1532            Table table = this;
1533            if (xmlExpr instanceof MondrianDef.Column) {
1534                final MondrianDef.Column xmlColumn =
1535                    (MondrianDef.Column) xmlExpr;
1536
1537                String tableName = xmlColumn.table;
1538                table = findAncestor(tableName);
1539                if (table == null) {
1540                    throw Util.newError(
1541                        "Level '" + level.getUniqueName()
1542                        + "' of cube '"
1543                        + this
1544                        + "' is invalid: table '" + tableName
1545                        + "' is not found in current scope"
1546                        + Util.nl
1547                        + ", star:"
1548                        + Util.nl
1549                        + getStar());
1550                }
1551                RolapStar.AliasReplacer aliasReplacer =
1552                    new RolapStar.AliasReplacer(tableName, table.getAlias());
1553                xmlExpr = aliasReplacer.visit(xmlExpr);
1554            }
1555            // does the column already exist??
1556            Column c = lookupColumnByExpression(xmlExpr);
1557
1558            RolapStar.Column column;
1559            // Verify Column is not null and not the same as the
1560            // nameColumn created previously (bug 1438285)
1561            if (c != null && !c.equals(nameColumn)) {
1562                // Yes, well just reuse it
1563                // You might wonder why the column need be returned if it
1564                // already exists. Well, it might have been created for one
1565                // cube, but for another cube using the same fact table, it
1566                // still needs to be put into the cube level to column map.
1567                // Trust me, return null and a junit test fails.
1568                column = c;
1569            } else {
1570                // Make a new column and add it
1571                column = new RolapStar.Column(
1572                    name,
1573                    table,
1574                    xmlExpr,
1575                    datatype,
1576                    internalType,
1577                    nameColumn,
1578                    parentColumn,
1579                    usagePrefix,
1580                    level.getApproxRowCount(),
1581                    star.nextColumnCount());
1582                addColumn(column);
1583            }
1584            return column;
1585        }
1586
1587        /**
1588         * Extends this 'leg' of the star by adding <code>relation</code>
1589         * joined by <code>joinCondition</code>. If the same expression is
1590         * already present, does not create it again. Stores the unaliased
1591         * table names to RolapStar.Table mapping associated with the
1592         * input <code>cube</code>.
1593         */
1594        synchronized Table addJoin(
1595            RolapCube cube,
1596            MondrianDef.RelationOrJoin relationOrJoin,
1597            RolapStar.Condition joinCondition)
1598        {
1599            if (relationOrJoin instanceof MondrianDef.Relation) {
1600                final MondrianDef.Relation relation =
1601                    (MondrianDef.Relation) relationOrJoin;
1602                RolapStar.Table starTable =
1603                    findChild(relation, joinCondition);
1604                if (starTable == null) {
1605                    starTable = new RolapStar.Table(
1606                        star, relation, this, joinCondition);
1607                    if (this.children.isEmpty()) {
1608                        this.children = new ArrayList<Table>();
1609                    }
1610                    this.children.add(starTable);
1611                }
1612                return starTable;
1613            } else if (relationOrJoin instanceof MondrianDef.Join) {
1614                MondrianDef.Join join = (MondrianDef.Join) relationOrJoin;
1615                RolapStar.Table leftTable =
1616                    addJoin(cube, join.left, joinCondition);
1617                String leftAlias = join.leftAlias;
1618                if (leftAlias == null) {
1619                    // REVIEW: is cast to Relation valid?
1620                    leftAlias = ((MondrianDef.Relation) join.left).getAlias();
1621                    if (leftAlias == null) {
1622                        throw Util.newError(
1623                            "missing leftKeyAlias in " + relationOrJoin);
1624                    }
1625                }
1626                assert leftTable.findAncestor(leftAlias) == leftTable;
1627                // switch to uniquified alias
1628                leftAlias = leftTable.getAlias();
1629
1630                String rightAlias = join.rightAlias;
1631                if (rightAlias == null) {
1632                    // the right relation of a join may be a join
1633                    // if so, we need to use the right relation join's
1634                    // left relation's alias.
1635                    if (join.right instanceof MondrianDef.Join) {
1636                        MondrianDef.Join joinright =
1637                            (MondrianDef.Join) join.right;
1638                        // REVIEW: is cast to Relation valid?
1639                        rightAlias =
1640                            ((MondrianDef.Relation) joinright.left)
1641                                .getAlias();
1642                    } else {
1643                        // REVIEW: is cast to Relation valid?
1644                        rightAlias =
1645                            ((MondrianDef.Relation) join.right)
1646                                .getAlias();
1647                    }
1648                    if (rightAlias == null) {
1649                        throw Util.newError(
1650                            "missing rightKeyAlias in " + relationOrJoin);
1651                    }
1652                }
1653                joinCondition = new RolapStar.Condition(
1654                    new MondrianDef.Column(leftAlias, join.leftKey),
1655                    new MondrianDef.Column(rightAlias, join.rightKey));
1656                RolapStar.Table rightTable = leftTable.addJoin(
1657                    cube, join.right, joinCondition);
1658                return rightTable;
1659
1660            } else {
1661                throw Util.newInternal("bad relation type " + relationOrJoin);
1662            }
1663        }
1664
1665        /**
1666         * Returns a child relation which maps onto a given relation, or null
1667         * if there is none.
1668         */
1669        public Table findChild(
1670            MondrianDef.Relation relation,
1671            Condition joinCondition)
1672        {
1673            for (Table child : getChildren()) {
1674                if (child.relation.equals(relation)) {
1675                    Condition condition = joinCondition;
1676                    if (!Util.equalName(relation.getAlias(), child.alias)) {
1677                        // Make the two conditions comparable, by replacing
1678                        // occurrence of this table's alias with occurrences
1679                        // of the child's alias.
1680                        AliasReplacer aliasReplacer = new AliasReplacer(
1681                            relation.getAlias(), child.alias);
1682                        condition = aliasReplacer.visit(joinCondition);
1683                    }
1684                    if (child.joinCondition.equals(condition)) {
1685                        return child;
1686                    }
1687                }
1688            }
1689            return null;
1690        }
1691
1692        /**
1693         * Returns a descendant with a given alias, or null if none found.
1694         */
1695        public Table findDescendant(String seekAlias) {
1696            if (getAlias().equals(seekAlias)) {
1697                return this;
1698            }
1699            for (Table child : getChildren()) {
1700                Table found = child.findDescendant(seekAlias);
1701                if (found != null) {
1702                    return found;
1703                }
1704            }
1705            return null;
1706        }
1707
1708        /**
1709         * Returns an ancestor with a given alias, or null if not found.
1710         */
1711        public Table findAncestor(String tableName) {
1712            for (Table t = this; t != null; t = t.parent) {
1713                if (t.relation.getAlias().equals(tableName)) {
1714                    return t;
1715                }
1716            }
1717            return null;
1718        }
1719
1720        public boolean equalsTableName(String tableName) {
1721            if (this.relation instanceof MondrianDef.Table) {
1722                MondrianDef.Table mt = (MondrianDef.Table) this.relation;
1723                if (mt.name.equals(tableName)) {
1724                    return true;
1725                }
1726            }
1727            return false;
1728        }
1729
1730        /**
1731         * Adds this table to the FROM clause of a query, and also, if
1732         * <code>joinToParent</code>, any join condition.
1733         *
1734         * @param query Query to add to
1735         * @param failIfExists Pass in false if you might have already added
1736         *     the table before and if that happens you want to do nothing.
1737         * @param joinToParent Pass in true if you are constraining a cell
1738         *     calculation, false if you are retrieving members.
1739         */
1740        public void addToFrom(
1741            SqlQuery query,
1742            boolean failIfExists,
1743            boolean joinToParent)
1744        {
1745            query.addFrom(relation, alias, failIfExists);
1746            Util.assertTrue((parent == null) == (joinCondition == null));
1747            if (joinToParent) {
1748                if (parent != null) {
1749                    parent.addToFrom(query, failIfExists, joinToParent);
1750                }
1751                if (joinCondition != null) {
1752                    query.addWhere(joinCondition.toString(query));
1753                }
1754            }
1755        }
1756
1757        /**
1758         * Returns a list of child {@link Table}s.
1759         */
1760        public List<Table> getChildren() {
1761            return children;
1762        }
1763
1764        /**
1765         * Returns a list of this table's {@link Column}s.
1766         */
1767        public List<Column> getColumns() {
1768            return columnList;
1769        }
1770
1771        /**
1772         * Finds the child table of the fact table with the given columnName
1773         * used in its left join condition. This is used by the AggTableManager
1774         * while characterizing the fact table columns.
1775         */
1776        public RolapStar.Table findTableWithLeftJoinCondition(
1777            final String columnName)
1778        {
1779            for (Table child : getChildren()) {
1780                Condition condition = child.joinCondition;
1781                if (condition != null) {
1782                    if (condition.left instanceof MondrianDef.Column) {
1783                        MondrianDef.Column mcolumn =
1784                            (MondrianDef.Column) condition.left;
1785                        if (mcolumn.name.equals(columnName)) {
1786                            return child;
1787                        }
1788                    }
1789                }
1790            }
1791            return null;
1792        }
1793
1794        /**
1795         * This is used during aggregate table validation to make sure that the
1796         * mapping from for the aggregate join condition is valid. It returns
1797         * the child table with the matching left join condition.
1798         */
1799        public RolapStar.Table findTableWithLeftCondition(
1800            final MondrianDef.Expression left)
1801        {
1802            for (Table child : getChildren()) {
1803                Condition condition = child.joinCondition;
1804                if (condition != null) {
1805                    if (condition.left instanceof MondrianDef.Column) {
1806                        MondrianDef.Column mcolumn =
1807                            (MondrianDef.Column) condition.left;
1808                        if (mcolumn.equals(left)) {
1809                            return child;
1810                        }
1811                    }
1812                }
1813            }
1814            return null;
1815        }
1816
1817        /**
1818         * Note: I do not think that this is ever true.
1819         */
1820        public boolean isFunky() {
1821            return (relation == null);
1822        }
1823
1824        public boolean equals(Object obj) {
1825            if (!(obj instanceof Table)) {
1826                return false;
1827            }
1828            Table other = (Table) obj;
1829            return getAlias().equals(other.getAlias());
1830        }
1831        public int hashCode() {
1832            return getAlias().hashCode();
1833        }
1834
1835        public String toString() {
1836            StringWriter sw = new StringWriter(256);
1837            PrintWriter pw = new PrintWriter(sw);
1838            print(pw, "");
1839            pw.flush();
1840            return sw.toString();
1841        }
1842
1843        /**
1844         * Prints this table and its children.
1845         */
1846        public void print(PrintWriter pw, String prefix) {
1847            pw.print(prefix);
1848            pw.println("Table:");
1849            String subprefix = prefix + "  ";
1850
1851            pw.print(subprefix);
1852            pw.print("alias=");
1853            pw.println(getAlias());
1854
1855            if (this.relation != null) {
1856                pw.print(subprefix);
1857                pw.print("relation=");
1858                pw.println(relation);
1859            }
1860
1861            pw.print(subprefix);
1862            pw.println("Columns:");
1863            String subsubprefix = subprefix + "  ";
1864
1865            for (Column column : getColumns()) {
1866                column.print(pw, subsubprefix);
1867                pw.println();
1868            }
1869
1870            if (this.joinCondition != null) {
1871                this.joinCondition.print(pw, subprefix);
1872            }
1873            for (Table child : getChildren()) {
1874                child.print(pw, subprefix);
1875            }
1876        }
1877
1878        /**
1879         * Returns whether this table has a column with the given name.
1880         */
1881        public boolean containsColumn(String columnName) {
1882            if (relation instanceof MondrianDef.Relation) {
1883                return star.containsColumn(
1884                    ((MondrianDef.Relation) relation).getAlias(),
1885                    columnName);
1886            } else {
1887                // todo: Deal with join.
1888                return false;
1889            }
1890        }
1891    }
1892
1893    public static class Condition {
1894        private static final Logger LOGGER = Logger.getLogger(Condition.class);
1895
1896        private final MondrianDef.Expression left;
1897        private final MondrianDef.Expression right;
1898        // set in Table constructor
1899        Table table;
1900
1901        Condition(
1902            MondrianDef.Expression left,
1903            MondrianDef.Expression right)
1904        {
1905            assert left != null;
1906            assert right != null;
1907
1908            if (!(left instanceof MondrianDef.Column)) {
1909                // TODO: Will this ever print?? if not then left should be
1910                // of type MondrianDef.Column.
1911                LOGGER.debug(
1912                    "Condition.left NOT Column: "
1913                    + left.getClass().getName());
1914            }
1915            this.left = left;
1916            this.right = right;
1917        }
1918        public MondrianDef.Expression getLeft() {
1919            return left;
1920        }
1921        public String getLeft(final SqlQuery query) {
1922            return this.left.getExpression(query);
1923        }
1924        public MondrianDef.Expression getRight() {
1925            return right;
1926        }
1927        public String getRight(final SqlQuery query) {
1928            return this.right.getExpression(query);
1929        }
1930        public String toString(SqlQuery query) {
1931            return left.getExpression(query) + " = "
1932                + right.getExpression(query);
1933        }
1934        public int hashCode() {
1935            return left.hashCode() ^ right.hashCode();
1936        }
1937
1938        public boolean equals(Object obj) {
1939            if (!(obj instanceof Condition)) {
1940                return false;
1941            }
1942            Condition that = (Condition) obj;
1943            return this.left.equals(that.left)
1944                && this.right.equals(that.right);
1945        }
1946
1947        public String toString() {
1948            StringWriter sw = new StringWriter(256);
1949            PrintWriter pw = new PrintWriter(sw);
1950            print(pw, "");
1951            pw.flush();
1952            return sw.toString();
1953        }
1954
1955        /**
1956         * Prints this table and its children.
1957         */
1958        public void print(PrintWriter pw, String prefix) {
1959            SqlQuery sqlQueuy = table.getSqlQuery();
1960            pw.print(prefix);
1961            pw.println("Condition:");
1962            String subprefix = prefix + "  ";
1963
1964            pw.print(subprefix);
1965            pw.print("left=");
1966            // print the foreign key bit position if we can figure it out
1967            if (left instanceof MondrianDef.Column) {
1968                MondrianDef.Column c = (MondrianDef.Column) left;
1969                Column col = table.star.getFactTable().lookupColumn(c.name);
1970                if (col != null) {
1971                    pw.print(" (");
1972                    pw.print(col.getBitPosition());
1973                    pw.print(") ");
1974                }
1975             }
1976            pw.println(left.getExpression(sqlQueuy));
1977
1978            pw.print(subprefix);
1979            pw.print("right=");
1980            pw.println(right.getExpression(sqlQueuy));
1981        }
1982    }
1983
1984    /**
1985     * Creates a copy of an expression, everywhere replacing one alias
1986     * with another.
1987     */
1988    public static class AliasReplacer {
1989        private final String oldAlias;
1990        private final String newAlias;
1991
1992        public AliasReplacer(String oldAlias, String newAlias) {
1993            this.oldAlias = oldAlias;
1994            this.newAlias = newAlias;
1995        }
1996
1997        private Condition visit(Condition condition) {
1998            if (condition == null) {
1999                return null;
2000            }
2001            if (newAlias.equals(oldAlias)) {
2002                return condition;
2003            }
2004            return new Condition(
2005                visit(condition.left),
2006                visit(condition.right));
2007        }
2008
2009        public MondrianDef.Expression visit(MondrianDef.Expression expression) {
2010            if (expression == null) {
2011                return null;
2012            }
2013            if (newAlias.equals(oldAlias)) {
2014                return expression;
2015            }
2016            if (expression instanceof MondrianDef.Column) {
2017                MondrianDef.Column column = (MondrianDef.Column) expression;
2018                return new MondrianDef.Column(visit(column.table), column.name);
2019            } else {
2020                throw Util.newInternal("need to implement " + expression);
2021            }
2022        }
2023
2024        private String visit(String table) {
2025            return table.equals(oldAlias)
2026                ? newAlias
2027                : table;
2028        }
2029    }
2030
2031    /**
2032     * Comparator to compare columns based on their name
2033     */
2034    public static class ColumnComparator implements Comparator<Column> {
2035
2036        public static ColumnComparator instance = new ColumnComparator();
2037
2038        private ColumnComparator() {
2039        }
2040
2041        public int compare(Column o1, Column o2) {
2042            return o1.getName().compareTo(o2.getName());
2043        }
2044    }
2045}
2046
2047// End RolapStar.java