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