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