KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > RolapStar


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

13
14 package mondrian.rolap;
15 import mondrian.olap.*;
16 import mondrian.rolap.agg.*;
17 import mondrian.rolap.aggmatcher.AggStar;
18 import mondrian.rolap.sql.SqlQuery;
19 import mondrian.spi.DataSourceChangeListener;
20
21 import org.apache.log4j.Logger;
22 import org.eigenbase.util.property.Property;
23 import org.eigenbase.util.property.TriggerBase;
24
25 import javax.sql.DataSource JavaDoc;
26 import java.io.PrintWriter JavaDoc;
27 import java.io.StringWriter JavaDoc;
28 import java.sql.Connection JavaDoc;
29 import java.sql.*;
30 import java.util.*;
31
32 /**
33  * A <code>RolapStar</code> is a star schema. It is the means to read cell
34  * values.
35  *
36  * <p>todo: put this in package which specicializes in relational aggregation,
37  * doesn't know anything about hierarchies etc.
38  *
39  * @author jhyde
40  * @since 12 August, 2001
41  * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapStar.java#71 $
42  */

43 public class RolapStar {
44     private static final Logger LOGGER = Logger.getLogger(RolapStar.class);
45
46     /**
47      * Controls the aggregate data cache for all RolapStars.
48      * An administrator or tester might selectively enable or
49      * disable in memory caching to allow direct measurement of database
50      * performance.
51      */

52     private static boolean disableCaching =
53         MondrianProperties.instance().DisableCaching.get();
54
55     static {
56         // Trigger is used to lookup and change the value of the
57
// variable that controls aggregate data caching
58
// Using a trigger means we don't have to look up the property eveytime.
59
MondrianProperties.instance().DisableCaching.addTrigger(
60             new TriggerBase(true) {
61                 public void execute(Property property, String JavaDoc value) {
62                     disableCaching = property.booleanValue();
63                     // must flush all caches
64
if (disableCaching) {
65                         RolapSchema.flushAllRolapStarCachedAggregations();
66                     }
67                 }
68             }
69         );
70     }
71
72
73     private final RolapSchema schema;
74
75     // not final for test purposes
76
private DataSource JavaDoc dataSource;
77
78     private final Table factTable;
79
80     /**
81      * Maps {@link RolapCube} to a {@link HashMap} which maps
82      * {@link RolapLevel} to {@link Column}. The double indirection is
83      * necessary because in different cubes, a shared hierarchy might be joined
84      * onto the fact table at different levels.
85      */

86     private final Map<RolapCube, Map<RolapLevel, Column>>
87         cubeToLevelToColumnMapMap;
88
89     /** Holds all global aggregations of this star. */
90     private Map<BitKey,Aggregation> aggregations;
91
92     /** Holds all thread-local aggregations of this star. */
93     private final ThreadLocal JavaDoc<Map<BitKey, Aggregation>>
94         localAggregations =
95             new ThreadLocal JavaDoc<Map<BitKey, Aggregation>>() {
96
97             protected Map<BitKey, Aggregation> initialValue() {
98                 return new HashMap<BitKey, Aggregation>();
99             }
100         };
101
102     /**
103      * Holds all pending aggregations of this star that are waiting to
104      * be pushed into the global cache. They cannot be pushed yet, because
105      * the aggregates in question are currently in use by other threads.
106      */

107     private final Map<BitKey, Aggregation> pendingAggregations;
108
109     /**
110      * Holds all requests for aggregations.
111      */

112     private final List<BitKey> aggregationRequests;
113
114     /**
115      * Holds all requests of aggregations per thread.
116      */

117     private final ThreadLocal JavaDoc<List<BitKey>> localAggregationRequests =
118         new ThreadLocal JavaDoc<List<BitKey>>() {
119             protected List<BitKey> initialValue() {
120                 return new ArrayList<BitKey>();
121             }
122         };
123
124     /**
125      * Number of columns (column and columnName).
126      */

127     private int columnCount;
128
129     private final SqlQuery.Dialect sqlQueryDialect;
130
131     /**
132      * If true, then database aggregation information is cached, otherwise
133      * it is flushed after each query.
134      */

135     private boolean cacheAggregations;
136
137     /**
138      * Partially ordered list of AggStars associated with this RolapStar's fact
139      * table
140      */

141     private List<AggStar> aggStars;
142
143     private DataSourceChangeListener changeListener;
144
145     /**
146      * Creates a RolapStar. Please use
147      * {@link RolapSchema.RolapStarRegistry#getOrCreateStar} to create a
148      * {@link RolapStar}.
149      */

150     RolapStar(
151             final RolapSchema schema,
152             final DataSource JavaDoc dataSource,
153             final MondrianDef.Relation fact) {
154         this.cacheAggregations = true;
155         this.schema = schema;
156         this.dataSource = dataSource;
157         this.factTable = new RolapStar.Table(this, fact, null, null);
158
159         this.cubeToLevelToColumnMapMap =
160             new HashMap<RolapCube, Map<RolapLevel, Column>>();
161         this.aggregations = new HashMap<BitKey, Aggregation>();
162         this.pendingAggregations = new HashMap<BitKey, Aggregation>();
163
164         this.aggregationRequests = new ArrayList<BitKey>();
165
166         clearAggStarList();
167
168         sqlQueryDialect = schema.getDialect();
169
170         changeListener = schema.getDataSourceChangeListener();
171     }
172
173     /**
174      * Returns this RolapStar's column count. After a star has been created with
175      * all of its columns, this is the number of columns in the star.
176      */

177     public int getColumnCount() {
178         return columnCount;
179     }
180
181     /**
182      * This is used by the {@link Column} constructor to get a unique id (per
183      * its parent {@link RolapStar}).
184      */

185     private int nextColumnCount() {
186         return columnCount++;
187     }
188
189     /**
190      * This is used to decrement the column counter and is used if a newly
191      * created column is found to already exist.
192      */

193     private int decrementColumnCount() {
194         return columnCount--;
195     }
196
197     /**
198      * This is a place holder in case in the future we wish to be able to
199      * reload aggregates. In that case, if aggregates had already been loaded,
200      * i.e., this star has some aggstars, then those aggstars are cleared.
201      */

202     public void prepareToLoadAggregates() {
203         aggStars = Collections.emptyList();
204     }
205
206     /**
207      * Adds an {@link AggStar} to this star.
208      *
209      * <p>Internally the AggStars are added in sort order, smallest row count
210      * to biggest, so that the most efficient AggStar is encountered first;
211      * ties do not matter.
212      */

213     public void addAggStar(AggStar aggStar) {
214         if (aggStars == Collections.EMPTY_LIST) {
215             // if this is NOT a LinkedList, then the insertion time is longer.
216
aggStars = new LinkedList<AggStar>();
217         }
218
219         // Add it before the first AggStar which is larger, if there is one.
220
int size = aggStar.getSize();
221         ListIterator<AggStar> lit = aggStars.listIterator();
222         while (lit.hasNext()) {
223             AggStar as = lit.next();
224             if (as.getSize() >= size) {
225                 lit.previous();
226                 lit.add(aggStar);
227                 return;
228             }
229         }
230
231         // There is no larger star. Add at the end of the list.
232
aggStars.add(aggStar);
233     }
234
235     /**
236      * Set the agg star list to empty.
237      */

238     void clearAggStarList() {
239         aggStars = Collections.emptyList();
240     }
241
242     /**
243      * Reorder the list of aggregate stars. This should be called if the
244      * algorithm used to order the AggStars has been changed.
245      */

246     public void reOrderAggStarList() {
247         // the order of these two lines is important
248
List<AggStar> l = aggStars;
249         clearAggStarList();
250
251         for (AggStar aggStar : l) {
252             addAggStar(aggStar);
253         }
254     }
255
256     /**
257      * Returns this RolapStar's aggregate table AggStars, ordered in ascending
258      * order of size.
259      */

260     public List<AggStar> getAggStars() {
261         return aggStars;
262     }
263
264     /**
265      * Returns the fact table at the center of this RolapStar.
266      *
267      * @return fact table
268      */

269     public Table getFactTable() {
270         return factTable;
271     }
272
273     /**
274      * Clones an existing SqlQuery to create a new one (this cloning creates one
275      * with an empty sql query).
276      */

277     public SqlQuery getSqlQuery() {
278         return new SqlQuery(getSqlQueryDialect());
279     }
280
281     /**
282      * Returns this RolapStar's SQL dialect.
283      */

284     public SqlQuery.Dialect getSqlQueryDialect() {
285         return sqlQueryDialect;
286     }
287
288     /**
289      * Maps a cube to a Map of level to colunms. Now the only reason
290      * the star needs to map via a cube is that more than one cube can
291      * share the same star.
292      *
293      * @param cube Cube
294      */

295     Map<RolapLevel, Column> getLevelToColumnMap(RolapCube cube) {
296         Map<RolapLevel, Column> levelToColumnMap =
297             this.cubeToLevelToColumnMapMap.get(cube);
298         if (levelToColumnMap == null) {
299             levelToColumnMap = new HashMap<RolapLevel, Column>();
300             this.cubeToLevelToColumnMapMap.put(cube, levelToColumnMap);
301         }
302         return levelToColumnMap;
303     }
304
305
306     /**
307      * Sets whether to cache database aggregation information; if false, cache
308      * is flushed after each query.
309      *
310      * <p>This method is called only by the RolapCube and is only called if
311      * caching is to be turned off. Note that the same RolapStar can be
312      * associated with more than on RolapCube. If any one of those cubes has
313      * caching turned off, then caching is turned off for all of them.
314      *
315      * @param cacheAggregations Whether to cache database aggregation
316      */

317     void setCacheAggregations(boolean cacheAggregations) {
318         // this can only change from true to false
319
this.cacheAggregations = cacheAggregations;
320         clearCachedAggregations(false);
321     }
322
323     /**
324      * Returns whether the this RolapStar cache aggregates.
325      *
326      * @see #setCacheAggregations(boolean)
327      */

328     boolean isCacheAggregations() {
329         return this.cacheAggregations;
330     }
331
332     /**
333      * Clears the aggregate cache. This only does something if aggregate caching
334      * is disabled (see {@link #setCacheAggregations(boolean)}).
335      *
336      * @param forced If true, clears cached aggregations regardless of any other
337      * settings. If false, clears only cache from the current thread
338      */

339     void clearCachedAggregations(boolean forced) {
340         if (forced || !cacheAggregations || RolapStar.disableCaching) {
341             if (LOGGER.isDebugEnabled()) {
342                 StringBuilder JavaDoc buf = new StringBuilder JavaDoc(100);
343                 buf.append("RolapStar.clearCachedAggregations: schema=");
344                 buf.append(schema.getName());
345                 buf.append(", star=");
346                 buf.append(getFactTable().getAlias());
347                 LOGGER.debug(buf.toString());
348             }
349
350             if (forced) {
351                 synchronized (aggregations) {
352                     aggregations.clear();
353                 }
354                 localAggregations.get().clear();
355             } else {
356                 // Only clear aggregation cache for the currect thread context.
357
localAggregations.get().clear();
358             }
359         }
360
361     }
362
363     /**
364      * Looks up an aggregation or creates one if it does not exist in an
365      * atomic (synchronized) operation.
366      *
367      * <p>When a new aggregation is created, it is marked as thread local.
368      *
369      * @param bitKey this is the contrained column bitkey
370      */

371     public Aggregation lookupOrCreateAggregation(final BitKey bitKey) {
372
373         Aggregation aggregation = lookupAggregation(bitKey);
374
375         if (aggregation == null) {
376             aggregation = new Aggregation(this, bitKey);
377
378             this.localAggregations.get().put(bitKey, aggregation);
379
380             // Let the change listener get the opportunity to register the
381
// first time the aggregation is used
382
if ((this.cacheAggregations) && (!RolapStar.disableCaching)) {
383                 if (changeListener != null) {
384                     Util.discard(changeListener.isAggregationChanged(aggregation));
385                 }
386             }
387         }
388         return aggregation;
389     }
390
391
392     /**
393      * Looks for an existing aggregation over a given set of columns, or
394      * returns <code>null</code> if there is none.
395      *
396      * <p>Thread local cache is taken first.
397      *
398      * <p>Must be called from synchronized context.
399      */

400     public Aggregation lookupAggregation(BitKey bitKey) {
401         // First try thread local cache
402
Aggregation aggregation = localAggregations.get().get(bitKey);
403         if (aggregation != null) {
404             return aggregation;
405         }
406
407         if (cacheAggregations && !RolapStar.disableCaching) {
408             // Look in global cache
409
synchronized (aggregations) {
410                 aggregation = aggregations.get(bitKey);
411                 if (aggregation != null) {
412                     // Keep track of global aggregates that a query is using
413
recordAggregationRequest(bitKey);
414                 }
415             }
416         }
417
418         return aggregation;
419     }
420
421     /**
422      * Checks whether an aggregation has changed since the last the time
423      * loaded.
424      *
425      * <p>If so, a new thread local aggregation will be made and added after
426      * the query has finished.
427      *
428      * <p>This method should be called before a query is executed and afterwards
429      * the function {@link #pushAggregateModificationsToGlobalCache()} should
430      * be called.
431      */

432     public void checkAggregateModifications() {
433
434         // Clear own aggregation requests at the beginning of a query
435
// made by request to materialize results after RolapResult constructor
436
// is finished
437
clearAggregationRequests();
438
439         if (changeListener != null) {
440             if (cacheAggregations && !RolapStar.disableCaching) {
441                 synchronized (aggregations) {
442                     for (Map.Entry<BitKey, Aggregation> e :
443                         aggregations.entrySet())
444                     {
445                         BitKey bitKey = e.getKey();
446
447                         Aggregation aggregation = e.getValue();
448                         if (changeListener.isAggregationChanged(aggregation)) {
449                             // Create new thread local aggregation
450
// This thread will renew aggregations
451
// And these will be checked in if all queries
452
// that are currently using these aggregates
453
// are finished
454
aggregation = new Aggregation(this, bitKey);
455
456                             localAggregations.get().put(bitKey, aggregation);
457                         }
458                     }
459                 }
460             }
461         }
462     }
463
464     /**
465      * Checks whether changed modifications may be pushed into global cache.
466      *
467      * <p>The method checks whether there are other running queries that are
468      * using the requested modifications. If this is the case, modifications
469      * are not pushed yet.
470      */

471     public void pushAggregateModificationsToGlobalCache() {
472         // Need synchronized access to both aggregationRequests as to
473
// aggregations, synchronize this instead
474
synchronized (this) {
475             if (cacheAggregations && !RolapStar.disableCaching) {
476
477                 // Push pending modifications other thread could not push
478
// to global cache, because it was in use
479
Iterator<Map.Entry<BitKey, Aggregation>>
480                     it = pendingAggregations.entrySet().iterator();
481                 while (it.hasNext()) {
482                     Map.Entry<BitKey, Aggregation> e = it.next();
483                     BitKey bitKey = e.getKey();
484                     Aggregation aggregation = e.getValue();
485                     // In case this aggregation is not requested by anyone
486
// this aggregation may be pushed into global cache
487
// otherwise put it in pending cache, that will be pushed
488
// when another query finishes
489
if (!isAggregationRequested(bitKey)) {
490                         pushAggregateModification(
491                             bitKey, aggregation,aggregations);
492                         it.remove();
493                     }
494                 }
495                 // Push thread local modifications
496
it = localAggregations.get().entrySet().iterator();
497                 while (it.hasNext()) {
498                     Map.Entry<BitKey, Aggregation> e = it.next();
499                     BitKey bitKey = e.getKey();
500                     Aggregation aggregation = e.getValue();
501                     // In case this aggregation is not requested by anyone
502
// this aggregation may be pushed into global cache
503
// otherwise put it in pending cache, that will be pushed
504
// when another query finishes
505
if (!isAggregationRequested(bitKey)) {
506                         pushAggregateModification(
507                             bitKey, aggregation, aggregations);
508                     } else {
509                         pushAggregateModification(
510                             bitKey, aggregation, pendingAggregations);
511                     }
512                 }
513                 localAggregations.get().clear();
514             }
515             // Clear own aggregation requests
516
clearAggregationRequests();
517         }
518     }
519
520     /**
521      * Pushes aggregations in destination aggregations, replacing older
522      * entries.
523      */

524     private void pushAggregateModification(
525         BitKey localBitKey,
526         Aggregation localAggregation,
527         Map<BitKey,Aggregation> destAggregations)
528     {
529         if (cacheAggregations && !RolapStar.disableCaching) {
530             synchronized (destAggregations) {
531
532                 boolean found = false;
533                 Iterator<Map.Entry<BitKey, Aggregation>>
534                         it = destAggregations.entrySet().iterator();
535                 while (it.hasNext()) {
536                     Map.Entry<BitKey, Aggregation> e =
537                         it.next();
538                     BitKey bitKey = e.getKey();
539                     Aggregation aggregation = e.getValue();
540
541                     if (localBitKey.equals(bitKey)) {
542
543                         if (localAggregation.getCreationTimestamp().after(
544                             aggregation.getCreationTimestamp())) {
545                             it.remove();
546                         } else {
547                             // Entry is newer, do not replace
548
found = true;
549                         }
550                         break;
551                     }
552                 }
553                 if (!found) {
554                     destAggregations.put(localBitKey, localAggregation);
555                 }
556             }
557         }
558     }
559
560     /**
561      * Records global cache requests per thread.
562      */

563     private void recordAggregationRequest(BitKey bitKey) {
564         if (!localAggregationRequests.get().contains(bitKey)) {
565             synchronized(aggregationRequests) {
566                 aggregationRequests.add(bitKey);
567             }
568             // Store own request for cleanup afterwards
569
localAggregationRequests.get().add(bitKey);
570         }
571     }
572
573     /**
574      * Checks whether an aggregation is requested by another thread.
575      */

576     private boolean isAggregationRequested(BitKey bitKey) {
577         synchronized (aggregationRequests) {
578             return aggregationRequests.contains(bitKey);
579         }
580     }
581
582     /**
583      * Clears the aggregation requests created by the current thread.
584      */

585     private void clearAggregationRequests() {
586         synchronized (aggregationRequests) {
587             if (localAggregationRequests.get().isEmpty()) {
588                 return;
589             }
590             // Build a set of requests for efficient probing. Negligible cost
591
// if this thread's localAggregationRequests is small, but avoids a
592
// quadratic algorithm if it is large.
593
Set<BitKey> localAggregationRequestSet =
594                 new HashSet<BitKey>(localAggregationRequests.get());
595             Iterator<BitKey> iter = aggregationRequests.iterator();
596             while (iter.hasNext()) {
597                 BitKey bitKey = iter.next();
598                 if (localAggregationRequestSet.contains(bitKey)) {
599                     iter.remove();
600                     // Make sure that bitKey is not removed more than once:
601
// other occurrences might exist for other threads.
602
localAggregationRequestSet.remove(bitKey);
603                     if (localAggregationRequestSet.isEmpty()) {
604                         // Nothing further to do
605
break;
606                     }
607                 }
608             }
609             localAggregationRequests.get().clear();
610         }
611     }
612
613     /** For testing purposes only. */
614     public void setDataSource(DataSource JavaDoc dataSource) {
615         this.dataSource = dataSource;
616     }
617
618     /**
619      * Returns the DataSource used to connect to the underlying DBMS.
620      *
621      * @return DataSource
622      */

623     public DataSource JavaDoc getDataSource() {
624         return dataSource;
625     }
626
627     /**
628      * Retrieves the {@link RolapStar.Measure} in which a measure is stored.
629      */

630     public static Measure getStarMeasure(Member member) {
631         return (Measure) ((RolapStoredMeasure) member).getStarMeasure();
632     }
633
634     /**
635      * Retrieves a named column, returns null if not found.
636      */

637     public Column[] lookupColumns(String JavaDoc tableAlias, String JavaDoc columnName) {
638         final Table table = factTable.findDescendant(tableAlias);
639         return (table == null) ? null : table.lookupColumns(columnName);
640     }
641
642     /**
643      * This is used by TestAggregationManager only.
644      */

645     public Column lookupColumn(String JavaDoc tableAlias, String JavaDoc columnName) {
646         final Table table = factTable.findDescendant(tableAlias);
647         return (table == null) ? null : table.lookupColumn(columnName);
648     }
649
650     /**
651      * Returns a list of all aliases used in this star.
652      */

653     public List<String JavaDoc> getAliasList() {
654         List<String JavaDoc> aliasList = new ArrayList<String JavaDoc>();
655         if (factTable != null) {
656             collectAliases(aliasList, factTable);
657         }
658         return aliasList;
659     }
660
661     /**
662      * Finds all of the table aliases in a table and its children.
663      */

664     private static void collectAliases(List<String JavaDoc> aliasList, Table table) {
665         aliasList.add(table.getAlias());
666         for (Table child : table.children) {
667             collectAliases(aliasList, child);
668         }
669     }
670
671     /**
672      * Collects all columns in this table and its children.
673      * If <code>joinColumn</code> is specified, only considers child tables
674      * joined by the given column.
675      */

676     public static void collectColumns(
677         Collection<Column> columnList,
678         Table table,
679         MondrianDef.Column joinColumn)
680     {
681         if (joinColumn == null) {
682             columnList.addAll(table.columnList);
683         }
684         for (Table child : table.children) {
685             if (joinColumn == null ||
686                 child.getJoinCondition().left.equals(joinColumn)) {
687                 collectColumns(columnList, child, null);
688             }
689         }
690     }
691
692     /**
693      * Reads a cell of <code>measure</code>, where <code>columns</code> are
694      * constrained to <code>values</code>. <code>values</code> must be the
695      * same length as <code>columns</code>; null values are left unconstrained.
696      */

697     Object JavaDoc getCell(CellRequest request) {
698         return getCell(request, dataSource);
699     }
700
701     private Object JavaDoc getCell(CellRequest request, DataSource JavaDoc dataSource) {
702         Measure measure = request.getMeasure();
703         Column[] columns = request.getConstrainedColumns();
704         Object JavaDoc[] values = request.getSingleValues();
705         Util.assertTrue(columns.length == values.length);
706         SqlQuery sqlQuery = getSqlQuery();
707         // add measure
708
Util.assertTrue(measure.getTable() == factTable);
709         factTable.addToFrom(sqlQuery, true, true);
710         sqlQuery.addSelect(
711             measure.aggregator.getExpression(
712                 measure.generateExprString(sqlQuery)));
713         // add constraining dimensions
714
for (int i = 0; i < columns.length; i++) {
715             Object JavaDoc value = values[i];
716             if (value == null) {
717                 continue; // not constrained
718
}
719             Column column = columns[i];
720             Table table = column.getTable();
721             if (table.isFunky()) {
722                 // this is a funky dimension -- ignore for now
723
continue;
724             }
725             table.addToFrom(sqlQuery, true, true);
726         }
727         String JavaDoc sql = sqlQuery.toString();
728         final SqlStatement stmt =
729             RolapUtil.executeQuery(
730                 dataSource, sql, "RolapStar.getCell",
731                 "while computing single cell");
732         try {
733             ResultSet resultSet = stmt.getResultSet();
734             Object JavaDoc o = null;
735             if (resultSet.next()) {
736                 o = resultSet.getObject(1);
737                 ++stmt.rowCount;
738             }
739             if (o == null) {
740                 o = Util.nullValue; // convert to placeholder
741
}
742             return o;
743         } catch (SQLException e) {
744             throw stmt.handle(e);
745         } finally {
746             stmt.close();
747         }
748     }
749
750     private boolean containsColumn(String JavaDoc tableName, String JavaDoc columnName) {
751         Connection JavaDoc jdbcConnection;
752         try {
753             jdbcConnection = dataSource.getConnection();
754         } catch (SQLException e1) {
755             throw Util.newInternal(
756                 e1, "Error while creating connection from data source");
757         }
758         try {
759             final DatabaseMetaData metaData = jdbcConnection.getMetaData();
760             final ResultSet columns =
761                 metaData.getColumns(null, null, tableName, columnName);
762             return columns.next();
763         } catch (SQLException e) {
764             throw Util.newInternal("Error while retrieving metadata for table '" +
765                             tableName + "', column '" + columnName + "'");
766         } finally {
767             try {
768                 jdbcConnection.close();
769             } catch (SQLException e) {
770                 // ignore
771
}
772         }
773     }
774
775     public RolapSchema getSchema() {
776         return schema;
777     }
778
779     public String JavaDoc toString() {
780         StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
781         PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
782         print(pw, "", true);
783         pw.flush();
784         return sw.toString();
785     }
786
787     /**
788      * Prints the state of this <code>RolapStar</code>
789      *
790      * @param pw Writer
791      * @param prefix Prefix to print at the start of each line
792      * @param structure Whether to print the structure of the star
793      */

794     public void print(PrintWriter JavaDoc pw, String JavaDoc prefix, boolean structure) {
795         if (structure) {
796             pw.print(prefix);
797             pw.println("RolapStar:");
798             String JavaDoc subprefix = prefix + " ";
799             factTable.print(pw, subprefix);
800
801             for (AggStar aggStar : getAggStars()) {
802                 aggStar.print(pw, subprefix);
803             }
804         }
805
806         List<Aggregation> aggregationList =
807             new ArrayList<Aggregation>(aggregations.values());
808         Collections.sort(
809             aggregationList,
810             new Comparator<Aggregation>() {
811                 public int compare(Aggregation o1, Aggregation o2) {
812                     return o1.getConstrainedColumnsBitKey().compareTo(
813                         o2.getConstrainedColumnsBitKey());
814                 }
815             }
816         );
817
818         for (Aggregation aggregation : aggregationList) {
819             aggregation.print(pw);
820         }
821     }
822
823     /**
824      * Flushes the contents of a given region of cells from this star.
825      *
826      * @param cacheControl Cache control API
827      * @param region Predicate defining a region of cells
828      */

829     public void flush(
830         CacheControl cacheControl,
831         CacheControl.CellRegion region)
832     {
833         // Translate the region into a set of (column, value) constraints.
834
final RolapCacheRegion cacheRegion =
835             RolapAggregationManager.makeCacheRegion(this, region);
836         for (Aggregation aggregation : aggregations.values()) {
837             aggregation.flush(cacheControl, cacheRegion);
838         }
839     }
840
841
842     /**
843      * Returns the listener for changes to this star's underlying database.
844      *
845      * @return Returns the Data source change listener.
846      */

847     public DataSourceChangeListener getChangeListener() {
848         return changeListener;
849     }
850
851     /**
852      * Sets the listener for changes to this star's underlying database.
853      *
854      * @param changeListener The Data source change listener to set
855      */

856     public void setChangeListener(DataSourceChangeListener changeListener) {
857         this.changeListener = changeListener;
858     }
859
860     // -- Inner classes --------------------------------------------------------
861

862     /**
863      * A column in a star schema.
864      */

865     public static class Column {
866         private final Table table;
867         private final MondrianDef.Expression expression;
868         private final SqlQuery.Datatype datatype;
869         private final String JavaDoc name;
870         /**
871          * When a Column is a column, and not a Measure, the parent column
872          * is the coloumn associated with next highest Level.
873          */

874         private final Column parentColumn;
875
876         /**
877          * This is used during both aggregate table recognition and aggregate
878          * table generation. For multiple dimension usages, multiple shared
879          * dimension or unshared dimension with the same column names,
880          * this is used to disambiguate aggregate column names.
881          */

882         private final String JavaDoc usagePrefix;
883         /**
884          * This is only used in RolapAggregationManager and adds
885          * non-constraining columns making the drill-through queries easier for
886          * humans to understand.
887          */

888         private final Column nameColumn;
889         private boolean isNameColumn;
890
891         /** this has a unique value per star */
892         private final int bitPosition;
893
894         private int cardinality = -1;
895
896         private Column(
897             String JavaDoc name,
898             Table table,
899             MondrianDef.Expression expression,
900             SqlQuery.Datatype datatype)
901         {
902             this(name, table, expression, datatype, null, null, null);
903         }
904
905         private Column(
906             String JavaDoc name,
907             Table table,
908             MondrianDef.Expression expression,
909             SqlQuery.Datatype datatype,
910             Column nameColumn,
911             Column parentColumn,
912             String JavaDoc usagePrefix)
913         {
914             this.name = name;
915             this.table = table;
916             this.expression = expression;
917             this.datatype = datatype;
918             this.bitPosition = table.star.nextColumnCount();
919             this.nameColumn = nameColumn;
920             this.parentColumn = parentColumn;
921             this.usagePrefix = usagePrefix;
922             if (nameColumn != null) {
923                 nameColumn.isNameColumn = true;
924             }
925         }
926
927         public boolean equals(Object JavaDoc obj) {
928             if (! (obj instanceof RolapStar.Column)) {
929                 return false;
930             }
931             RolapStar.Column other = (RolapStar.Column) obj;
932             // Note: both columns have to be from the same table
933
return (other.table == this.table) &&
934                    other.expression.equals(this.expression) &&
935                    (other.datatype == this.datatype) &&
936                    other.name.equals(this.name);
937         }
938
939         public String JavaDoc getName() {
940             return name;
941         }
942
943         public int getBitPosition() {
944             return bitPosition;
945         }
946
947         public RolapStar getStar() {
948             return table.star;
949         }
950
951         public RolapStar.Table getTable() {
952             return table;
953         }
954
955         public SqlQuery getSqlQuery() {
956             return getTable().getStar().getSqlQuery();
957         }
958
959         public RolapStar.Column getNameColumn() {
960             return nameColumn;
961         }
962
963         public RolapStar.Column getParentColumn() {
964             return parentColumn;
965         }
966
967         public String JavaDoc getUsagePrefix() {
968             return usagePrefix;
969         }
970
971         public boolean isNameColumn() {
972             return isNameColumn;
973         }
974
975         public MondrianDef.Expression getExpression() {
976             return expression;
977         }
978
979         /**
980          * Generates a SQL expression, which typically this looks like
981          * this: <code><i>tableName</i>.<i>columnName</i></code>.
982          */

983         public String JavaDoc generateExprString(SqlQuery query) {
984             return getExpression().getExpression(query);
985         }
986
987         public int getCardinality() {
988             if (cardinality == -1) {
989                 cardinality = getCardinality(getStar().getDataSource());
990             }
991             return cardinality;
992         }
993
994         private int getCardinality(DataSource JavaDoc dataSource) {
995             SqlQuery sqlQuery = getSqlQuery();
996             if (sqlQuery.getDialect().allowsCountDistinct()) {
997                 // e.g. "select count(distinct product_id) from product"
998
sqlQuery.addSelect("count(distinct "
999                     + generateExprString(sqlQuery) + ")");
1000
1001                // no need to join fact table here
1002
table.addToFrom(sqlQuery, true, false);
1003            } else if (sqlQuery.getDialect().allowsFromQuery()) {
1004                // Some databases (e.g. Access) don't like 'count(distinct)',
1005
// so use, e.g., "select count(*) from (select distinct
1006
// product_id from product)"
1007
SqlQuery inner = sqlQuery.cloneEmpty();
1008                inner.setDistinct(true);
1009                inner.addSelect(generateExprString(inner));
1010                boolean failIfExists = true,
1011                    joinToParent = false;
1012                table.addToFrom(inner, failIfExists, joinToParent);
1013                sqlQuery.addSelect("count(*)");
1014                sqlQuery.addFrom(inner, "init", failIfExists);
1015            } else {
1016                throw Util.newInternal("Cannot compute cardinality: this " +
1017                    "database neither supports COUNT DISTINCT nor SELECT in " +
1018                    "the FROM clause.");
1019            }
1020            String JavaDoc sql = sqlQuery.toString();
1021            final SqlStatement stmt =
1022                RolapUtil.executeQuery(
1023                    dataSource, sql,
1024                    "RolapStar.Column.getCardinality",
1025                    "while counting distinct values of column '" +
1026                    expression.getGenericExpression());
1027            try {
1028                ResultSet resultSet = stmt.getResultSet();
1029                Util.assertTrue(resultSet.next());
1030                ++stmt.rowCount;
1031                return resultSet.getInt(1);
1032            } catch (SQLException e) {
1033                throw stmt.handle(e);
1034            } finally {
1035                stmt.close();
1036            }
1037        }
1038
1039        /**
1040         * Generates a predicate that a column matches one of a list of values.
1041         *
1042         * <p>
1043         * Several possible outputs, depending upon whether the there are
1044         * nulls:<ul>
1045         *
1046         * <li>One not-null value: <code>foo.bar = 1</code>
1047         *
1048         * <li>All values not null: <code>foo.bar in (1, 2, 3)</code></li
1049         *
1050         * <li>Null and not null values:
1051         * <code>(foo.bar is null or foo.bar in (1, 2))</code></li>
1052         *
1053         * <li>Only null values:
1054         * <code>foo.bar is null</code></li>
1055         *
1056         * <li>String values: <code>foo.bar in ('a', 'b', 'c')</code></li>
1057         *
1058         * </ul>
1059         */

1060        public static String JavaDoc createInExpr(
1061            String JavaDoc expr,
1062            StarColumnPredicate predicate,
1063            SqlQuery.Datatype datatype,
1064            SqlQuery.Dialect dialect)
1065        {
1066            if (predicate instanceof ValueColumnPredicate) {
1067                final ValueColumnPredicate valuePredicate =
1068                    (ValueColumnPredicate) predicate;
1069                Object JavaDoc key = valuePredicate.getValue();
1070                if (key == RolapUtil.sqlNullValue) {
1071                    return expr + " is null";
1072                } else {
1073                    StringBuilder JavaDoc buf = new StringBuilder JavaDoc(64);
1074                    buf.append(expr);
1075                    buf.append(" = ");
1076                    dialect.quote(buf, key, datatype);
1077                    return buf.toString();
1078                }
1079            } else if (predicate instanceof ListColumnPredicate) {
1080                ListColumnPredicate valueListColumnPredicate =
1081                    (ListColumnPredicate) predicate;
1082                List<StarColumnPredicate> predicates =
1083                    valueListColumnPredicate.getPredicates();
1084                if (predicates.size() == 1) {
1085                    return createInExpr(
1086                        expr, predicates.get(0), datatype, dialect);
1087                }
1088
1089                int notNullCount = 0;
1090                StringBuilder JavaDoc sb = new StringBuilder JavaDoc(expr);
1091                ValueColumnPredicate firstNotNull = null;
1092                sb.append(" in (");
1093                for (StarColumnPredicate predicate1 : predicates) {
1094                    final ValueColumnPredicate predicate2 =
1095                        (ValueColumnPredicate) predicate1;
1096                    Object JavaDoc key = predicate2.getValue();
1097                    if (key == RolapUtil.sqlNullValue) {
1098                        continue;
1099                    }
1100                    if (notNullCount > 0) {
1101                        sb.append(", ");
1102                    } else {
1103                        firstNotNull = predicate2;
1104                    }
1105                    ++notNullCount;
1106                    dialect.quote(sb, key, datatype);
1107                }
1108                sb.append(')');
1109                if (notNullCount < predicates.size()) {
1110                    // There was at least one null.
1111
StringBuilder JavaDoc buf;
1112                    switch (notNullCount) {
1113                    case 0:
1114                        // Special case -- there were no values besides null.
1115
// Return, for example, "x is null".
1116
return expr + " is null";
1117                    case 1:
1118                        // Special case -- one not-null value, and null, for
1119
// example "(x = 1 or x is null)".
1120
assert firstNotNull != null;
1121                        buf = new StringBuilder JavaDoc(64);
1122                        buf.append('(');
1123                        buf.append(expr);
1124                        buf.append(" = ");
1125                        dialect.quote(
1126                            buf,
1127                            firstNotNull.getValue(),
1128                            datatype);
1129                        buf.append(" or ");
1130                        buf.append(expr);
1131                        buf.append(" is null)");
1132                        return buf.toString();
1133                    default:
1134                        // Nulls and values, for example,
1135
// "(x in (1, 2) or x IS NULL)".
1136
buf = new StringBuilder JavaDoc(64);
1137                        buf.append('(');
1138                        buf.append(sb.toString());
1139                        buf.append(" or ");
1140                        buf.append(expr);
1141                        buf.append(" is null)");
1142                        return buf.toString();
1143                    }
1144                } else {
1145                    // No nulls. Return, for example, "x in (1, 2, 3)".
1146
return sb.toString();
1147                }
1148            } else if (predicate instanceof LiteralStarPredicate) {
1149                return predicate.toString();
1150            } else {
1151                throw Util.newInternal(
1152                    "Unexpected constraint type: " + predicate);
1153            }
1154        }
1155
1156        public String JavaDoc toString() {
1157            StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
1158            PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
1159            print(pw, "");
1160            pw.flush();
1161            return sw.toString();
1162        }
1163
1164        /**
1165         * Prints this table and its children.
1166         */

1167        public void print(PrintWriter JavaDoc pw, String JavaDoc prefix) {
1168            SqlQuery sqlQuery = getSqlQuery();
1169            pw.print(prefix);
1170            pw.print(getName());
1171            pw.print(" (");
1172            pw.print(getBitPosition());
1173            pw.print("): ");
1174            pw.print(generateExprString(sqlQuery));
1175        }
1176
1177        public SqlQuery.Datatype getDatatype() {
1178            return datatype;
1179        }
1180    }
1181
1182    /**
1183     * Definition of a measure in a star schema.
1184     *
1185     * <p>A measure is basically just a column; except that its
1186     * {@link #aggregator} defines how it is to be rolled up.
1187     */

1188    public static class Measure extends Column {
1189        private final RolapAggregator aggregator;
1190
1191        private Measure(
1192                String JavaDoc name,
1193                RolapAggregator aggregator,
1194                Table table,
1195                MondrianDef.Expression expression,
1196                SqlQuery.Datatype datatype) {
1197            super(name, table, expression, datatype);
1198            this.aggregator = aggregator;
1199        }
1200
1201        public RolapAggregator getAggregator() {
1202            return aggregator;
1203        }
1204
1205        public boolean equals(Object JavaDoc obj) {
1206            if (! super.equals(obj)) {
1207                return false;
1208            }
1209            if (! (obj instanceof RolapStar.Measure)) {
1210                return false;
1211            }
1212            RolapStar.Measure other = (RolapStar.Measure) obj;
1213            // Note: both measure have to have the same aggregator
1214
return (other.aggregator == this.aggregator);
1215        }
1216
1217        /**
1218         * Prints this table and its children.
1219         */

1220        public void print(PrintWriter JavaDoc pw, String JavaDoc prefix) {
1221            SqlQuery sqlQuery = getSqlQuery();
1222            pw.print(prefix);
1223            pw.print(getName());
1224            pw.print(" (");
1225            pw.print(getBitPosition());
1226            pw.print("): ");
1227            pw.print(aggregator.getExpression(generateExprString(sqlQuery)));
1228        }
1229    }
1230
1231    /**
1232     * Definition of a table in a star schema.
1233     *
1234     * <p>A 'table' is defined by a
1235     * {@link mondrian.olap.MondrianDef.Relation} so may, in fact, be a view.
1236     *
1237     * <p>Every table in the star schema except the fact table has a parent
1238     * table, and a condition which specifies how it is joined to its parent.
1239     * So the star schema is, in effect, a hierarchy with the fact table at
1240     * its root.
1241     */

1242    public static class Table {
1243        private final RolapStar star;
1244        private final MondrianDef.Relation relation;
1245        private final List<Column> columnList;
1246        private final Table parent;
1247        private List<Table> children;
1248        private final Condition joinCondition;
1249        private final String JavaDoc alias;
1250
1251        private Table(
1252                RolapStar star,
1253                MondrianDef.Relation relation,
1254                Table parent,
1255                Condition joinCondition) {
1256            this.star = star;
1257            this.relation = relation;
1258            Util.assertTrue(
1259                    relation instanceof MondrianDef.Table ||
1260                    relation instanceof MondrianDef.View,
1261                    "todo: allow dimension which is not a Table or View, [" +
1262                    relation + "]");
1263            this.alias = chooseAlias();
1264            this.parent = parent;
1265            final AliasReplacer aliasReplacer =
1266                    new AliasReplacer(relation.getAlias(), this.alias);
1267            this.joinCondition = aliasReplacer.visit(joinCondition);
1268            if (this.joinCondition != null) {
1269                this.joinCondition.table = this;
1270            }
1271            this.columnList = new ArrayList<Column>();
1272            this.children = Collections.emptyList();
1273            Util.assertTrue((parent == null) == (joinCondition == null));
1274        }
1275
1276        /**
1277         * Returns the condition by which a dimension table is connected to its
1278         * {@link #getParentTable() parent}; or null if this is the fact table.
1279         */

1280        public Condition getJoinCondition() {
1281            return joinCondition;
1282        }
1283
1284        /**
1285         * Returns this table's parent table, or null if this is the fact table
1286         * (which is at the center of the star).
1287         */

1288        public Table getParentTable() {
1289            return parent;
1290        }
1291
1292        private void addColumn(Column column) {
1293            columnList.add(column);
1294        }
1295
1296        /**
1297         * Adds to a list all columns of this table or a child table
1298         * which are present in a given bitKey.
1299         *
1300         * <p>Note: This method is slow, but that's acceptable because it is
1301         * only used for tracing. It would be more efficient to store an
1302         * array in the {@link RolapStar} mapping column ordinals to columns.
1303         */

1304        private void collectColumns(BitKey bitKey, List<Column> list) {
1305            for (Column column : getColumns()) {
1306                if (bitKey.get(column.getBitPosition())) {
1307                    list.add(column);
1308                }
1309            }
1310            for (Table table : getChildren()) {
1311                table.collectColumns(bitKey, list);
1312            }
1313        }
1314
1315        /**
1316         * Returns an array of all columns in this star with a given name.
1317         */

1318        public Column[] lookupColumns(String JavaDoc columnName) {
1319            List<Column> l = new ArrayList<Column>();
1320            for (Iterator<Column> it = getColumns().iterator(); it.hasNext(); ) {
1321                Column column = it.next();
1322                if (column.getExpression() instanceof MondrianDef.Column) {
1323                    MondrianDef.Column columnExpr =
1324                        (MondrianDef.Column) column.getExpression();
1325                    if (columnExpr.name.equals(columnName)) {
1326                        l.add(column);
1327                    }
1328                }
1329            }
1330            return (Column[]) l.toArray(new Column[0]);
1331        }
1332
1333        public Column lookupColumn(String JavaDoc columnName) {
1334            for (Iterator<Column> it = getColumns().iterator(); it.hasNext(); ) {
1335                Column column = it.next();
1336                if (column.getExpression() instanceof MondrianDef.Column) {
1337                    MondrianDef.Column columnExpr =
1338                        (MondrianDef.Column) column.getExpression();
1339                    if (columnExpr.name.equals(columnName)) {
1340                        return column;
1341                    }
1342                }
1343            }
1344            return null;
1345        }
1346
1347        /**
1348         * Given a MondrianDef.Expression return a column with that expression
1349         * or null.
1350         */

1351        public Column lookupColumnByExpression(MondrianDef.Expression xmlExpr) {
1352            for (Iterator<Column> it = getColumns().iterator(); it.hasNext(); ) {
1353                Column column = it.next();
1354                if (column instanceof RolapStar.Measure) {
1355                    continue;
1356                }
1357                if (column.getExpression().equals(xmlExpr)) {
1358                    return column;
1359                }
1360            }
1361            return null;
1362        }
1363
1364        public boolean containsColumn(Column column) {
1365            for (Iterator<Column> it = getColumns().iterator(); it.hasNext(); ) {
1366                Column other = it.next();
1367                if (column.equals(other)) {
1368                    return true;
1369                }
1370            }
1371            return false;
1372        }
1373
1374        /**
1375         * Look up a {@link Measure} by its name.
1376         * Returns null if not found.
1377         */

1378        public Measure lookupMeasureByName(String JavaDoc name) {
1379            for (Iterator<Column> it = getColumns().iterator(); it.hasNext(); ) {
1380                Column column = it.next();
1381                if (column instanceof Measure) {
1382                    Measure measure = (Measure) column;
1383                    if (measure.getName().equals(name)) {
1384                        return measure;
1385                    }
1386                }
1387            }
1388            return null;
1389        }
1390
1391        RolapStar getStar() {
1392            return star;
1393        }
1394        private SqlQuery getSqlQuery() {
1395            return getStar().getSqlQuery();
1396        }
1397        public MondrianDef.Relation getRelation() {
1398            return relation;
1399        }
1400
1401        /** Chooses an alias which is unique within the star. */
1402        private String JavaDoc chooseAlias() {
1403            List<String JavaDoc> aliasList = star.getAliasList();
1404            for (int i = 0;; ++i) {
1405                String JavaDoc candidateAlias = relation.getAlias();
1406                if (i > 0) {
1407                    candidateAlias += "_" + i;
1408                }
1409                if (!aliasList.contains(candidateAlias)) {
1410                    return candidateAlias;
1411                }
1412            }
1413        }
1414
1415        public String JavaDoc getAlias() {
1416            return alias;
1417        }
1418
1419        /**
1420         * Sometimes one need to get to the "real" name when the table has
1421         * been given an alias.
1422         */

1423        public String JavaDoc getTableName() {
1424            if (relation instanceof MondrianDef.Table) {
1425                MondrianDef.Table t = (MondrianDef.Table) relation;
1426                return t.name;
1427            } else {
1428                return null;
1429            }
1430        }
1431        synchronized void makeMeasure(RolapBaseCubeMeasure measure) {
1432            RolapStar.Measure starMeasure = new RolapStar.Measure(
1433                measure.getName(),
1434                measure.getAggregator(),
1435                this,
1436                measure.getMondrianDefExpression(),
1437                measure.getDatatype());
1438
1439            measure.setStarMeasure(starMeasure); // reverse mapping
1440

1441            if (containsColumn(starMeasure)) {
1442                star.decrementColumnCount();
1443            } else {
1444                addColumn(starMeasure);
1445            }
1446        }
1447
1448        /**
1449         * This is only called by RolapCube. If the RolapLevel has a non-null
1450         * name expression then two columns will be made, otherwise only one.
1451         *
1452         * @param cube
1453         * @param level
1454         * @param parentColumn
1455         */

1456        synchronized Column makeColumns(
1457                RolapCube cube,
1458                RolapLevel level,
1459                Column parentColumn,
1460                String JavaDoc usagePrefix) {
1461
1462            Column nameColumn = null;
1463            if (level.getNameExp() != null) {
1464                // make a column for the name expression
1465
nameColumn = makeColumnForLevelExpr(
1466                    cube,
1467                    level,
1468                    level.getName(),
1469                    level.getNameExp(),
1470                    SqlQuery.Datatype.String,
1471                    null,
1472                    null,
1473                    null);
1474            }
1475
1476            // select the column's name depending upon whether or not a
1477
// "named" column, above, has been created.
1478
String JavaDoc name = (level.getNameExp() == null)
1479                ? level.getName()
1480                : level.getName() + " (Key)";
1481
1482            // If the nameColumn is not null, then it is associated with this
1483
// column.
1484
Column column = makeColumnForLevelExpr(
1485                cube,
1486                level,
1487                name,
1488                level.getKeyExp(),
1489                level.getDatatype(),
1490                nameColumn,
1491                parentColumn,
1492                usagePrefix);
1493
1494            if (column != null) {
1495                Map<RolapLevel, Column> map = star.getLevelToColumnMap(cube);
1496                map.put(level, column);
1497            }
1498
1499            return column;
1500        }
1501
1502        private Column makeColumnForLevelExpr(
1503                RolapCube cube,
1504                RolapLevel level,
1505                String JavaDoc name,
1506                MondrianDef.Expression xmlExpr,
1507                SqlQuery.Datatype datatype,
1508                Column nameColumn,
1509                Column parentColumn,
1510                String JavaDoc usagePrefix) {
1511            Table table = this;
1512            if (xmlExpr instanceof MondrianDef.Column) {
1513                final MondrianDef.Column xmlColumn =
1514                    (MondrianDef.Column) xmlExpr;
1515
1516                String JavaDoc tableName = xmlColumn.table;
1517                table = findAncestor(tableName);
1518                if (table == null) {
1519                    throw Util.newError(
1520                            "Level '" + level.getUniqueName()
1521                            + "' of cube '"
1522                            + this
1523                            + "' is invalid: table '" + tableName
1524                            + "' is not found in current scope"
1525                            + Util.nl
1526                            + ", star:"
1527                            + Util.nl
1528                            + getStar());
1529                }
1530                RolapStar.AliasReplacer aliasReplacer =
1531                        new RolapStar.AliasReplacer(tableName, table.getAlias());
1532                xmlExpr = aliasReplacer.visit(xmlExpr);
1533            }
1534            // does the column already exist??
1535
Column c = lookupColumnByExpression(xmlExpr);
1536
1537            RolapStar.Column column = null;
1538            if (c != null) {
1539                // Yes, well just reuse it
1540
// You might wonder why the column need be returned if it
1541
// already exists. Well, it might have been created for one
1542
// cube, but for another cube using the same fact table, it
1543
// still needs to be put into the cube level to column map.
1544
// Trust me, return null and a junit test fails.
1545
column = c;
1546            } else {
1547                // Make a new column and add it
1548
column = new RolapStar.Column(
1549                    name,
1550                    table,
1551                    xmlExpr,
1552                    datatype,
1553                    nameColumn,
1554                    parentColumn,
1555                    usagePrefix);
1556                addColumn(column);
1557            }
1558            return column;
1559        }
1560
1561
1562        /**
1563         * Extends this 'leg' of the star by adding <code>relation</code>
1564         * joined by <code>joinCondition</code>. If the same expression is
1565         * already present, does not create it again.
1566         */

1567        synchronized Table addJoin(MondrianDef.Relation relation,
1568                                   RolapStar.Condition joinCondition) {
1569            if (relation instanceof MondrianDef.Table ||
1570                    relation instanceof MondrianDef.View) {
1571                RolapStar.Table starTable = findChild(relation, joinCondition);
1572                if (starTable == null) {
1573                    starTable = new RolapStar.Table(star, relation, this,
1574                        joinCondition);
1575                    if (this.children.isEmpty()) {
1576                        this.children = new ArrayList<Table>();
1577                    }
1578                    this.children.add(starTable);
1579                }
1580                return starTable;
1581
1582            } else if (relation instanceof MondrianDef.Join) {
1583                MondrianDef.Join join = (MondrianDef.Join) relation;
1584                RolapStar.Table leftTable = addJoin(join.left, joinCondition);
1585                String JavaDoc leftAlias = join.leftAlias;
1586                if (leftAlias == null) {
1587                    leftAlias = join.left.getAlias();
1588                    if (leftAlias == null) {
1589                        throw Util.newError(
1590                                "missing leftKeyAlias in " + relation);
1591                    }
1592                }
1593                assert leftTable.findAncestor(leftAlias) == leftTable;
1594                // switch to uniquified alias
1595
leftAlias = leftTable.getAlias();
1596
1597                String JavaDoc rightAlias = join.rightAlias;
1598                if (rightAlias == null) {
1599                    rightAlias = join.right.getAlias();
1600                    if (rightAlias == null) {
1601                        throw Util.newError(
1602                                "missing rightKeyAlias in " + relation);
1603                    }
1604                }
1605                joinCondition = new RolapStar.Condition(
1606                        new MondrianDef.Column(leftAlias, join.leftKey),
1607                        new MondrianDef.Column(rightAlias, join.rightKey));
1608                RolapStar.Table rightTable = leftTable.addJoin(
1609                        join.right, joinCondition);
1610                return rightTable;
1611
1612            } else {
1613                throw Util.newInternal("bad relation type " + relation);
1614            }
1615        }
1616
1617        /**
1618         * Returns a child relation which maps onto a given relation, or null if
1619         * there is none.
1620         */

1621        public Table findChild(
1622            MondrianDef.Relation relation,
1623            Condition joinCondition) {
1624            for (Table child : getChildren()) {
1625                if (child.relation.equals(relation)) {
1626                    Condition condition = joinCondition;
1627                    if (!Util.equalName(relation.getAlias(), child.alias)) {
1628                        // Make the two conditions comparable, by replacing
1629
// occurrence of this table's alias with occurrences
1630
// of the child's alias.
1631
AliasReplacer aliasReplacer = new AliasReplacer(
1632                            relation.getAlias(), child.alias);
1633                        condition = aliasReplacer.visit(joinCondition);
1634                    }
1635                    if (child.joinCondition.equals(condition)) {
1636                        return child;
1637                    }
1638                }
1639            }
1640            return null;
1641        }
1642
1643        /**
1644         * Returns a descendant with a given alias, or null if none found.
1645         */

1646        public Table findDescendant(String JavaDoc seekAlias) {
1647            if (getAlias().equals(seekAlias)) {
1648                return this;
1649            }
1650            for (Table child : getChildren()) {
1651                Table found = child.findDescendant(seekAlias);
1652                if (found != null) {
1653                    return found;
1654                }
1655            }
1656            return null;
1657        }
1658
1659        /**
1660         * Returns an ancestor with a given alias, or null if not found.
1661         */

1662        public Table findAncestor(String JavaDoc tableName) {
1663            for (Table t = this; t != null; t = t.parent) {
1664                if (t.relation.getAlias().equals(tableName)) {
1665                    return t;
1666                }
1667            }
1668            return null;
1669        }
1670
1671        public boolean equalsTableName(String JavaDoc tableName) {
1672            if (this.relation instanceof MondrianDef.Table) {
1673                MondrianDef.Table mt = (MondrianDef.Table) this.relation;
1674                if (mt.name.equals(tableName)) {
1675                    return true;
1676                }
1677            }
1678            return false;
1679        }
1680
1681        /**
1682         * Adds this table to the FROM clause of a query, and also, if
1683         * <code>joinToParent</code>, any join condition.
1684         *
1685         * @param query Query to add to
1686         * @param failIfExists Pass in false if you might have already added
1687         * the table before and if that happens you want to do nothing.
1688         * @param joinToParent Pass in true if you are constraining a cell
1689         * calculation, false if you are retrieving members.
1690         */

1691        public void addToFrom(
1692            SqlQuery query,
1693            boolean failIfExists,
1694            boolean joinToParent) {
1695            query.addFrom(relation, alias, failIfExists);
1696            Util.assertTrue((parent == null) == (joinCondition == null));
1697            if (joinToParent) {
1698                if (parent != null) {
1699                    parent.addToFrom(query, failIfExists, joinToParent);
1700                }
1701                if (joinCondition != null) {
1702                    query.addWhere(joinCondition.toString(query));
1703                }
1704            }
1705        }
1706
1707        /**
1708         * Returns a list of child {@link Table}s.
1709         */

1710        public List<Table> getChildren() {
1711            return children;
1712        }
1713
1714        /**
1715         * Returns a list of this table's {@link Column}s.
1716         */

1717        public List<Column> getColumns() {
1718            return columnList;
1719        }
1720
1721        /**
1722         * Finds the child table of the fact table with the given columnName
1723         * used in its left join condition. This is used by the AggTableManager
1724         * while characterizing the fact table columns.
1725         */

1726        public RolapStar.Table findTableWithLeftJoinCondition(
1727                final String JavaDoc columnName) {
1728            for (Table child : getChildren()) {
1729                Condition condition = child.joinCondition;
1730                if (condition != null) {
1731                    if (condition.left instanceof MondrianDef.Column) {
1732                        MondrianDef.Column mcolumn =
1733                            (MondrianDef.Column) condition.left;
1734                        if (mcolumn.name.equals(columnName)) {
1735                            return child;
1736                        }
1737                    }
1738                }
1739
1740            }
1741            return null;
1742        }
1743
1744        /**
1745         * This is used during aggregate table validation to make sure that the
1746         * mapping from for the aggregate join condition is valid. It returns
1747         * the child table with the matching left join condition.
1748         */

1749        public RolapStar.Table findTableWithLeftCondition(
1750                final MondrianDef.Expression left) {
1751            for (Table child : getChildren()) {
1752                Condition condition = child.joinCondition;
1753                if (condition != null) {
1754                    if (condition.left instanceof MondrianDef.Column) {
1755                        MondrianDef.Column mcolumn =
1756                            (MondrianDef.Column) condition.left;
1757                        if (mcolumn.equals(left)) {
1758                            return child;
1759                        }
1760                    }
1761                }
1762
1763            }
1764            return null;
1765        }
1766
1767        /**
1768         * Note: I do not think that this is ever true.
1769         */

1770        public boolean isFunky() {
1771            return (relation == null);
1772        }
1773        public boolean equals(Object JavaDoc obj) {
1774            if (!(obj instanceof Table)) {
1775                return false;
1776            }
1777            Table other = (Table) obj;
1778            return getAlias().equals(other.getAlias());
1779        }
1780        public int hashCode() {
1781            return getAlias().hashCode();
1782        }
1783
1784        public String JavaDoc toString() {
1785            StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
1786            PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
1787            print(pw, "");
1788            pw.flush();
1789            return sw.toString();
1790        }
1791
1792        /**
1793         * Prints this table and its children.
1794         */

1795        public void print(PrintWriter JavaDoc pw, String JavaDoc prefix) {
1796            pw.print(prefix);
1797            pw.println("Table:");
1798            String JavaDoc subprefix = prefix + " ";
1799
1800            pw.print(subprefix);
1801            pw.print("alias=");
1802            pw.println(getAlias());
1803
1804            if (this.relation != null) {
1805                pw.print(subprefix);
1806                pw.print("relation=");
1807                pw.println(relation);
1808            }
1809
1810            pw.print(subprefix);
1811            pw.println("Columns:");
1812            String JavaDoc subsubprefix = subprefix + " ";
1813
1814            for (Iterator<Column> it = getColumns().iterator(); it.hasNext(); ) {
1815                Column column = it.next();
1816                column.print(pw, subsubprefix);
1817                pw.println();
1818            }
1819
1820            if (this.joinCondition != null) {
1821                this.joinCondition.print(pw, subprefix);
1822            }
1823            for (Table child : getChildren()) {
1824                child.print(pw, subprefix);
1825            }
1826        }
1827
1828        /**
1829         * Returns whether this table has a column with the given name.
1830         */

1831        public boolean containsColumn(String JavaDoc columnName) {
1832            if (relation instanceof MondrianDef.Table) {
1833                return star.containsColumn(((MondrianDef.Table) relation).name,
1834                    columnName);
1835            } else {
1836                // todo: Deal with join and view.
1837
return false;
1838            }
1839        }
1840    }
1841
1842    public static class Condition {
1843        private static final Logger LOGGER = Logger.getLogger(Condition.class);
1844
1845        private final MondrianDef.Expression left;
1846        private final MondrianDef.Expression right;
1847        // set in Table constructor
1848
Table table;
1849
1850        Condition(MondrianDef.Expression left,
1851                  MondrianDef.Expression right) {
1852            assert left != null;
1853            assert right != null;
1854
1855            if (!(left instanceof MondrianDef.Column)) {
1856                // TODO: Will this ever print?? if not then left should be
1857
// of type MondrianDef.Column.
1858
LOGGER.debug("Condition.left NOT Column: "
1859                    + left.getClass().getName());
1860            }
1861            this.left = left;
1862            this.right = right;
1863        }
1864        public MondrianDef.Expression getLeft() {
1865            return left;
1866        }
1867        public MondrianDef.Expression getRight() {
1868            return right;
1869        }
1870        public String JavaDoc toString(SqlQuery query) {
1871            return left.getExpression(query) + " = " + right.getExpression(query);
1872        }
1873        public int hashCode() {
1874            return left.hashCode() ^ right.hashCode();
1875        }
1876
1877        public boolean equals(Object JavaDoc obj) {
1878            if (!(obj instanceof Condition)) {
1879                return false;
1880            }
1881            Condition that = (Condition) obj;
1882            return (this.left.equals(that.left) &&
1883                    this.right.equals(that.right));
1884        }
1885
1886        public String JavaDoc toString() {
1887            StringWriter JavaDoc sw = new StringWriter JavaDoc(256);
1888            PrintWriter JavaDoc pw = new PrintWriter JavaDoc(sw);
1889            print(pw, "");
1890            pw.flush();
1891            return sw.toString();
1892        }
1893
1894        /**
1895         * Prints this table and its children.
1896         */

1897        public void print(PrintWriter JavaDoc pw, String JavaDoc prefix) {
1898            SqlQuery sqlQueuy = table.getSqlQuery();
1899            pw.print(prefix);
1900            pw.println("Condition:");
1901            String JavaDoc subprefix = prefix + " ";
1902
1903            pw.print(subprefix);
1904            pw.print("left=");
1905            // print the foreign key bit position if we can figure it out
1906
if (left instanceof MondrianDef.Column) {
1907                MondrianDef.Column c = (MondrianDef.Column) left;
1908                Column col = table.star.getFactTable().lookupColumn(c.name);
1909                if (col != null) {
1910                    pw.print(" (");
1911                    pw.print(col.getBitPosition());
1912                    pw.print(") ");
1913                }
1914             }
1915            pw.println(left.getExpression(sqlQueuy));
1916
1917            pw.print(subprefix);
1918            pw.print("right=");
1919            pw.println(right.getExpression(sqlQueuy));
1920        }
1921    }
1922
1923    /**
1924     * Creates a copy of an expression, everywhere replacing one alias
1925     * with another.
1926     */

1927    public static class AliasReplacer {
1928        private final String JavaDoc oldAlias;
1929        private final String JavaDoc newAlias;
1930
1931        public AliasReplacer(String JavaDoc oldAlias, String JavaDoc newAlias) {
1932            this.oldAlias = oldAlias;
1933            this.newAlias = newAlias;
1934        }
1935
1936        private Condition visit(Condition condition) {
1937            if (condition == null) {
1938                return null;
1939            }
1940            if (newAlias.equals(oldAlias)) {
1941                return condition;
1942            }
1943            return new Condition(
1944                    visit(condition.left),
1945                    visit(condition.right));
1946        }
1947
1948        public MondrianDef.Expression visit(MondrianDef.Expression expression) {
1949            if (expression == null) {
1950                return null;
1951            }
1952            if (newAlias.equals(oldAlias)) {
1953                return expression;
1954            }
1955            if (expression instanceof MondrianDef.Column) {
1956                MondrianDef.Column column = (MondrianDef.Column) expression;
1957                return new MondrianDef.Column(visit(column.table), column.name);
1958            } else {
1959                throw Util.newInternal("need to implement " + expression);
1960            }
1961        }
1962
1963        private String JavaDoc visit(String JavaDoc table) {
1964            return table.equals(oldAlias)
1965                ? newAlias
1966                : table;
1967        }
1968    }
1969}
1970
1971// End RolapStar.java
1972
Popular Tags