KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > mondrian > rolap > aggmatcher > Recognizer


1 /*
2 // $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/Recognizer.java#20 $
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) 2005-2007 Julian Hyde and others
7 // All Rights Reserved.
8 // You must accept the terms of that agreement to use this software.
9 */

10
11 package mondrian.rolap.aggmatcher;
12
13 import mondrian.olap.Hierarchy;
14 import mondrian.olap.Dimension;
15 import mondrian.olap.MondrianDef;
16 import mondrian.resource.MondrianResource;
17 import mondrian.recorder.MessageRecorder;
18 import mondrian.rolap.RolapStar;
19 import mondrian.rolap.RolapLevel;
20 import mondrian.rolap.RolapSchema;
21 import mondrian.rolap.RolapCube;
22 import mondrian.rolap.RolapAggregator;
23 import mondrian.rolap.HierarchyUsage;
24 import mondrian.rolap.sql.SqlQuery;
25
26 import java.util.*;
27
28 import org.apache.log4j.Logger;
29
30 /**
31  * Abstract Recognizer class used to determine if a candidate aggregate table
32  * has the column categories: "fact_count" column, measure columns, foreign key
33  * and level columns.
34  *
35  * <p>Derived classes use either the default or explicit column descriptions in
36  * matching column categories. The basic matching algorithm is in this class
37  * while some specific column category matching and column building must be
38  * specified in derived classes.
39  *
40  * <p>A Recognizer is created per candidate aggregate table. The tables columns are
41  * then categorized. All errors and warnings are added to a MessageRecorder.
42  *
43  * <p>This class is less about defining a type and more about code sharing.
44  *
45  * @author Richard M. Emberson
46  * @version $Id: //open/mondrian/src/main/mondrian/rolap/aggmatcher/Recognizer.java#20 $
47  */

48 abstract class Recognizer {
49
50     private static final MondrianResource mres = MondrianResource.instance();
51     private static final Logger LOGGER = Logger.getLogger(Recognizer.class);
52     /**
53      * This is used to wrap column name matching rules.
54      */

55     public interface Matcher {
56
57         /**
58          * Return true it the name matches and false otherwise.
59          */

60         boolean matches(String JavaDoc name);
61     }
62
63     protected final RolapStar star;
64     protected final JdbcSchema.Table dbFactTable;
65     protected final JdbcSchema.Table aggTable;
66     protected final MessageRecorder msgRecorder;
67     protected boolean returnValue;
68
69     protected Recognizer(final RolapStar star,
70                          final JdbcSchema.Table dbFactTable,
71                          final JdbcSchema.Table aggTable,
72                          final MessageRecorder msgRecorder) {
73         this.star = star;
74         this.dbFactTable = dbFactTable;
75         this.aggTable = aggTable;
76         this.msgRecorder = msgRecorder;
77
78         returnValue = true;
79     }
80
81     /**
82      * Return true if the candidate aggregate table was successfully mapped into
83      * the fact table. This is the top-level checking method.
84      * <p>
85      * It first checks the ignore columns.
86      * <p>
87      * Next, the existence of a fact count column is checked.
88      * <p>
89      * Then the measures are checked. First the specified (defined,
90      * explicit) measures are all determined. There must be at least one such
91      * measure. This if followed by checking for implied measures (e.g., if base
92      * fact table as both sum and average of a column and the aggregate has a
93      * sum measure, the there is an implied average measure in the aggregate).
94      * <p>
95      * Now the levels are checked. This is in two parts. First, foreign keys are
96      * checked followed by level columns (for collapsed dimension aggregates).
97      * <p>
98      * If eveything checks out, returns true.
99      */

100     public boolean check() {
101         checkIgnores();
102         checkFactCount();
103
104         // Check measures
105
int nosMeasures = checkMeasures();
106         // There must be at least one measure
107
checkNosMeasures(nosMeasures);
108         generateImpliedMeasures();
109
110         // Check levels
111
List<JdbcSchema.Table.Column.Usage> notSeenForeignKeys = checkForeignKeys();
112 //printNotSeenForeignKeys(notSeenForeignKeys);
113
checkLevels(notSeenForeignKeys);
114
115         if (returnValue) {
116             // Add all unused columns as warning to the MessageRecorder
117
checkUnusedColumns();
118         }
119
120         return returnValue;
121     }
122
123     /**
124      * Return the ignore column Matcher.
125      */

126     protected abstract Matcher getIgnoreMatcher();
127
128     /**
129      * Check all columns to be marked as ignore.
130      */

131     protected void checkIgnores() {
132         Matcher ignoreMatcher = getIgnoreMatcher();
133
134         for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) {
135             if (ignoreMatcher.matches(aggColumn.getName())) {
136                 makeIgnore(aggColumn);
137             }
138         }
139     }
140
141     /**
142      * Create an ignore usage for the aggColumn.
143      *
144      * @param aggColumn
145      */

146     protected void makeIgnore(final JdbcSchema.Table.Column aggColumn) {
147         JdbcSchema.Table.Column.Usage usage =
148             aggColumn.newUsage(JdbcSchema.UsageType.IGNORE);
149         usage.setSymbolicName("Ignore");
150     }
151
152
153
154     /**
155      * Return the fact count column Matcher.
156      */

157     protected abstract Matcher getFactCountMatcher();
158
159     /**
160      * Make sure that the aggregate table has one fact count column and that its
161      * type is numeric.
162      */

163     protected void checkFactCount() {
164         msgRecorder.pushContextName("Recognizer.checkFactCount");
165
166         try {
167
168             Matcher factCountMatcher = getFactCountMatcher();
169
170             int nosOfFactCounts = 0;
171             for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) {
172                 // if marked as ignore, then do not consider
173
if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) {
174                     continue;
175                 }
176                 if (factCountMatcher.matches(aggColumn.getName())) {
177                     if (aggColumn.getDatatype().isNumeric()) {
178                         makeFactCount(aggColumn);
179                         nosOfFactCounts++;
180                     } else {
181                         String JavaDoc msg = mres.NonNumericFactCountColumn.str(
182                                 aggTable.getName(),
183                                 dbFactTable.getName(),
184                                 aggColumn.getName(),
185                                 aggColumn.getTypeName());
186                         msgRecorder.reportError(msg);
187
188                         returnValue = false;
189                     }
190                 }
191
192             }
193             if (nosOfFactCounts == 0) {
194                 String JavaDoc msg = mres.NoFactCountColumns.str(
195                         aggTable.getName(),
196                         dbFactTable.getName());
197                 msgRecorder.reportError(msg);
198
199                 returnValue = false;
200
201             } else if (nosOfFactCounts > 1) {
202                 String JavaDoc msg = mres.TooManyFactCountColumns.str(
203                         aggTable.getName(),
204                         dbFactTable.getName(),
205                         nosOfFactCounts);
206                 msgRecorder.reportError(msg);
207
208                 returnValue = false;
209             }
210
211         } finally {
212             msgRecorder.popContextName();
213         }
214     }
215
216
217     /**
218      * Check all measure columns returning the number of measure columns.
219      */

220     protected abstract int checkMeasures();
221
222     /**
223      * Create a fact count usage for the aggColumn.
224      *
225      * @param aggColumn
226      */

227     protected void makeFactCount(final JdbcSchema.Table.Column aggColumn) {
228         JdbcSchema.Table.Column.Usage usage =
229             aggColumn.newUsage(JdbcSchema.UsageType.FACT_COUNT);
230         usage.setSymbolicName("Fact Count");
231     }
232
233
234     /**
235      * Make sure there was at least one measure column identified.
236      *
237      * @param nosMeasures
238      */

239     protected void checkNosMeasures(int nosMeasures) {
240         msgRecorder.pushContextName("Recognizer.checkNosMeasures");
241
242         try {
243             if (nosMeasures == 0) {
244                 String JavaDoc msg = mres.NoMeasureColumns.str(
245                         aggTable.getName(),
246                         dbFactTable.getName()
247                     );
248                 msgRecorder.reportError(msg);
249
250                 returnValue = false;
251             }
252
253         } finally {
254             msgRecorder.popContextName();
255         }
256     }
257
258     /**
259      * An implied measure in an aggregate table is one where there is both a sum
260      * and average measures in the base fact table and the aggregate table has
261      * either a sum or average, the other measure is implied and can be
262      * generated from the measure and the fact_count column.
263      * <p>
264      * For each column in the fact table, get its measure usages. If there is
265      * both an average and sum aggregator associated with the column, then
266      * iterator over all of the column usage of type measure of the aggregator
267      * table. If only one aggregate column usage measure is found and this
268      * RolapStar.Measure measure instance variable is the same as the
269      * the fact table's usage's instance variable, then the other measure is
270      * implied and the measure is created for the aggregate table.
271      */

272     protected void generateImpliedMeasures() {
273         for (JdbcSchema.Table.Column factColumn : aggTable.getColumns()) {
274             JdbcSchema.Table.Column.Usage sumFactUsage = null;
275             JdbcSchema.Table.Column.Usage avgFactUsage = null;
276
277             for (Iterator<JdbcSchema.Table.Column.Usage> mit =
278                     factColumn.getUsages(JdbcSchema.UsageType.MEASURE);
279                     mit.hasNext(); ) {
280                 JdbcSchema.Table.Column.Usage factUsage = mit.next();
281                 if (factUsage.getAggregator() == RolapAggregator.Avg) {
282                     avgFactUsage = factUsage;
283                 } else if (factUsage.getAggregator() == RolapAggregator.Sum) {
284                     sumFactUsage = factUsage;
285                 }
286             }
287
288             if (avgFactUsage != null && sumFactUsage != null) {
289                 JdbcSchema.Table.Column.Usage sumAggUsage = null;
290                 JdbcSchema.Table.Column.Usage avgAggUsage = null;
291                 int seenCount = 0;
292                 for (Iterator<JdbcSchema.Table.Column.Usage> mit =
293                     aggTable.getColumnUsages(JdbcSchema.UsageType.MEASURE);
294                         mit.hasNext(); ) {
295
296                     JdbcSchema.Table.Column.Usage aggUsage = mit.next();
297                     if (aggUsage.rMeasure == avgFactUsage.rMeasure) {
298                         avgAggUsage = aggUsage;
299                         seenCount++;
300                     } else if (aggUsage.rMeasure == sumFactUsage.rMeasure) {
301                         sumAggUsage = aggUsage;
302                         seenCount++;
303                     }
304                 }
305                 if (seenCount == 1) {
306                     if (avgAggUsage != null) {
307                         makeMeasure(sumFactUsage, avgAggUsage);
308                     }
309                     if (sumAggUsage != null) {
310                         makeMeasure(avgFactUsage, sumAggUsage);
311                     }
312                 }
313             }
314         }
315     }
316
317     /**
318      * Here we have the fact usage of either sum or avg and an aggregate usage
319      * of the opposite type. We wish to make a new aggregate usage based
320      * on the existing usage's column of the same type as the fact usage.
321      *
322      * @param factUsage fact usage
323      * @param aggSiblingUsage existing sibling usage
324      */

325     protected void makeMeasure(final JdbcSchema.Table.Column.Usage factUsage,
326                                final JdbcSchema.Table.Column.Usage aggSiblingUsage) {
327         JdbcSchema.Table.Column aggColumn = aggSiblingUsage.getColumn();
328
329         JdbcSchema.Table.Column.Usage aggUsage =
330             aggColumn.newUsage(JdbcSchema.UsageType.MEASURE);
331
332         aggUsage.setSymbolicName(factUsage.getSymbolicName());
333         RolapAggregator ra = convertAggregator(
334                         aggUsage,
335                         factUsage.getAggregator(),
336                         aggSiblingUsage.getAggregator());
337         aggUsage.setAggregator(ra);
338         aggUsage.rMeasure = factUsage.rMeasure;
339     }
340
341     /**
342      * This method creates an aggregate table column measure usage from a fact
343      * table column measure usage.
344      *
345      * @param factUsage
346      * @param aggColumn
347      */

348     protected void makeMeasure(final JdbcSchema.Table.Column.Usage factUsage,
349                                final JdbcSchema.Table.Column aggColumn) {
350         JdbcSchema.Table.Column.Usage aggUsage =
351             aggColumn.newUsage(JdbcSchema.UsageType.MEASURE);
352
353         aggUsage.setSymbolicName(factUsage.getSymbolicName());
354         RolapAggregator ra =
355                 convertAggregator(aggUsage, factUsage.getAggregator());
356         aggUsage.setAggregator(ra);
357         aggUsage.rMeasure = factUsage.rMeasure;
358     }
359
360     /**
361      * This method determine how may aggregate table column's match the fact
362      * table foreign key column return in the number matched. For each matching
363      * column a foreign key usage is created.
364      */

365     protected abstract int matchForeignKey(JdbcSchema.Table.Column.Usage factUsage);
366
367     /**
368      * This method checks the foreign key columns.
369      * <p>
370      * For each foreign key column usage in the fact table, determine how many
371      * aggregate table columns match that column usage. If there is more than
372      * one match, then that is an error. If there were no matches, then the
373      * foreign key usage is added to the list of fact column foreign key that
374      * were not in the aggregate table. This list is returned by this method.
375      * <p>
376      * This matches foreign keys that were not "lost" or "collapsed".
377      *
378      * @return list on not seen foreign key column usages
379      */

380     protected List<JdbcSchema.Table.Column.Usage> checkForeignKeys() {
381         msgRecorder.pushContextName("Recognizer.checkForeignKeys");
382
383         try {
384
385             List<JdbcSchema.Table.Column.Usage> notSeenForeignKeys =
386                 Collections.emptyList();
387
388             for (Iterator<JdbcSchema.Table.Column.Usage> it =
389                 dbFactTable.getColumnUsages(JdbcSchema.UsageType.FOREIGN_KEY);
390                     it.hasNext(); ) {
391
392                 JdbcSchema.Table.Column.Usage factUsage = it.next();
393
394                 int matchCount = matchForeignKey(factUsage);
395
396                 if (matchCount > 1) {
397                     String JavaDoc msg = mres.TooManyMatchingForeignKeyColumns.str(
398                             aggTable.getName(),
399                             dbFactTable.getName(),
400                             matchCount,
401                             factUsage.getColumn().getName()
402                         );
403                     msgRecorder.reportError(msg);
404
405                     returnValue = false;
406
407                 } else if (matchCount == 0) {
408                     if (notSeenForeignKeys.isEmpty()) {
409                         notSeenForeignKeys = new ArrayList<JdbcSchema.Table.Column.Usage>();
410                     }
411                     notSeenForeignKeys.add(factUsage);
412                 }
413             }
414             return notSeenForeignKeys;
415
416         } finally {
417             msgRecorder.popContextName();
418         }
419     }
420
421     /**
422      * This method identifies those columns in the aggregate table that match
423      * "collapsed" dimension columns. Remember that a collapsed dimension is one
424      * where the higher levels of some hierarchy are columns in the aggregate
425      * table (and all of the lower levels are missing - it has aggregated up to
426      * the first existing level).
427      * <p>
428      * Here, we do not start from the fact table, we iterator over each cube.
429      * For each of the cube's dimensions, the dimension's hirarchies are
430      * iterated over. In turn, each hierarchy's usage is iterated over.
431      * if the hierarchy's usage's foreign key is not in the list of not seen
432      * foreign keys (the notSeenForeignKeys parameter), then that hierarchy is
433      * not considered. If the hierarchy's usage's foreign key is in the not seen
434      * list, then starting with the hierarchy's top level, it is determined if
435      * the combination of hierarchy, hierarchy usage, and level matches an
436      * aggregated table column. If so, then a level usage is created for that
437      * column and the hierarchy's next level is considered and so on until a
438      * for a level an aggregate table column does not match. Then we continue
439      * iterating over the hierarchy usages.
440      * <p>
441      * This check is different. The others mine the fact table usages. This
442      * looks through the fact table's cubes' dimension, hierarchy,
443      * hiearchy usages, levels to match up symbolic names for levels. The other
444      * checks match on "physical" characteristics, the column name; this matches
445      * on "logical" characteristics.
446      * <p>
447      * Note: Levels should not be created for foreign keys that WERE seen.
448      * Currently, this is NOT checked explicitly. For the explicit rules any
449      * extra columns MUST ge declared ignored or one gets an error.
450      *
451      * @param notSeenForeignKeys
452      */

453     protected void checkLevels(List<JdbcSchema.Table.Column.Usage> notSeenForeignKeys) {
454
455         // These are the factTable that do not appear in the aggTable.
456
// 1) find all cubes with this given factTable
457
// 1) per cube, find all usages with the column as foreign key
458
// 2) for each usage, find dimension and its levels
459
// 3) determine if level columns are represented
460

461         // In generaly, there is only one cube.
462
for (RolapCube cube : findCubes()) {
463             Dimension[] dims = cube.getDimensions();
464             // start dimensions at 1 (0 is measures)
465
for (int j = 1; j < dims.length; j++) {
466                 Dimension dim = dims[j];
467                 // Ok, got dimension.
468
// See if any of the levels exist as columns in the
469
// aggTable. This requires applying a map from:
470
// hierarchyName
471
// levelName
472
// levelColumnName
473
// to each "unassigned" column in the aggTable.
474
// Remember that the rule is if a level does appear,
475
// then all of the higher levels must also appear.
476
String JavaDoc dimName = dim.getName();
477
478                 Hierarchy[] hierarchies = dim.getHierarchies();
479                 for (Hierarchy hierarchy : hierarchies) {
480                     HierarchyUsage[] hierarchyUsages =
481                         cube.getUsages(hierarchy);
482                     for (HierarchyUsage hierarchyUsage : hierarchyUsages) {
483                         // Search through the notSeenForeignKeys list
484
// making sure that this HierarchyUsage's
485
// foreign key is not in the list.
486
String JavaDoc foreignKey = hierarchyUsage.getForeignKey();
487                         boolean b = inNotSeenForeignKeys(
488                             foreignKey,
489                             notSeenForeignKeys);
490                         if (!b) {
491                             // It was not in the not seen list, so ignore
492
continue;
493                         }
494
495
496                         RolapLevel[] levels =
497                             (RolapLevel[]) hierarchy.getLevels();
498                         // If the top level is seen, then one or more
499
// lower levels may appear but there can be no
500
// missing levels between the top level and
501
// lowest level seen.
502
// On the other hand, if the top level is not
503
// seen, then no other levels should be present.
504
mid_level:
505                         for (RolapLevel level : levels) {
506                             if (level.isAll()) {
507                                 continue mid_level;
508                             }
509                             if (matchLevel(hierarchy, hierarchyUsage, level)) {
510
511                                 continue mid_level;
512
513                             } else {
514                                 // There were no matches, break
515
// For now, do not check lower levels
516
break mid_level;
517                             }
518                         }
519                     }
520                 }
521             }
522         }
523     }
524
525     /**
526      * Return true if the foreignKey column name is in the list of not seen
527      * foreign keys.
528      */

529     boolean inNotSeenForeignKeys(
530         String JavaDoc foreignKey,
531         List<JdbcSchema.Table.Column.Usage> notSeenForeignKeys)
532     {
533         for (JdbcSchema.Table.Column.Usage usage : notSeenForeignKeys) {
534             if (usage.getColumn().getName().equals(foreignKey)) {
535                 return true;
536             }
537         }
538         return false;
539     }
540
541     /**
542      * Debug method: Print out not seen foreign key list.
543      *
544      * @param notSeenForeignKeys
545      */

546     private void printNotSeenForeignKeys(List notSeenForeignKeys) {
547         LOGGER.debug("Recognizer.printNotSeenForeignKeys: "
548             + aggTable.getName());
549         for (Iterator it = notSeenForeignKeys.iterator(); it.hasNext(); ) {
550             JdbcSchema.Table.Column.Usage usage =
551                 (JdbcSchema.Table.Column.Usage) it.next();
552             LOGGER.debug(" " + usage.getColumn().getName());
553         }
554     }
555
556     /**
557      * Here a measure ussage is created and the right join condition is
558      * explicitly supplied. This is needed is when the aggregate table's column
559      * names may not match those found in the RolapStar.
560      *
561      * @param factUsage
562      * @param aggColumn
563      * @param rightJoinConditionColumnName
564      */

565     protected void makeForeignKey(final JdbcSchema.Table.Column.Usage factUsage,
566                                   final JdbcSchema.Table.Column aggColumn,
567                                   final String JavaDoc rightJoinConditionColumnName) {
568         JdbcSchema.Table.Column.Usage aggUsage =
569             aggColumn.newUsage(JdbcSchema.UsageType.FOREIGN_KEY);
570         aggUsage.setSymbolicName("FOREIGN_KEY");
571         // Extract from RolapStar enough stuff to build
572
// AggStar subtable except the column name of the right join
573
// condition might be different
574
aggUsage.rTable = factUsage.rTable;
575         aggUsage.rightJoinConditionColumnName = rightJoinConditionColumnName;
576
577         aggUsage.rColumn = factUsage.rColumn;
578     }
579
580     /**
581      * Match a aggregate table column given the hierarchy, hierarchy usage, and
582      * rolap level returning true if a match is found.
583      */

584     protected abstract boolean matchLevel(
585             final Hierarchy hierarchy,
586             final HierarchyUsage hierarchyUsage,
587             final RolapLevel level);
588
589     /**
590      * Make a level column usage.
591      *
592      * <p> Note there is a check in this code. If a given aggregate table
593      * column has already has a level usage, then that usage must all refer to
594      * the same hierarchy usage join table and column name as the one that
595      * calling this method was to create. If there is an existing level usage
596      * for the column and it matches something else, then it is an error.
597      */

598     protected void makeLevel(
599             final JdbcSchema.Table.Column aggColumn,
600             final Hierarchy hierarchy,
601             final HierarchyUsage hierarchyUsage,
602             final String JavaDoc factColumnName,
603             final String JavaDoc levelColumnName,
604             final String JavaDoc symbolicName) {
605
606         msgRecorder.pushContextName("Recognizer.makeLevel");
607
608         try {
609
610         if (aggColumn.hasUsage(JdbcSchema.UsageType.LEVEL)) {
611             // The column has at least one usage of level type
612
// make sure we are looking at the
613
// same table and column
614
for (Iterator<JdbcSchema.Table.Column.Usage> uit =
615                 aggColumn.getUsages(JdbcSchema.UsageType.LEVEL);
616                     uit.hasNext(); ) {
617                 JdbcSchema.Table.Column.Usage aggUsage = uit.next();
618
619                 MondrianDef.Relation rel = hierarchyUsage.getJoinTable();
620                 String JavaDoc cName = levelColumnName;
621
622                 if (! aggUsage.relation.equals(rel) ||
623                     ! aggColumn.column.name.equals(cName)) {
624
625                     // this is an error so return
626
String JavaDoc msg = mres.DoubleMatchForLevel.str(
627                         aggTable.getName(),
628                         dbFactTable.getName(),
629                         aggColumn.getName(),
630                         aggUsage.relation.toString(),
631                         aggColumn.column.name,
632                         rel.toString(),
633                         cName);
634                     msgRecorder.reportError(msg);
635
636                     returnValue = false;
637
638                     msgRecorder.throwRTException();
639                 }
640             }
641         } else {
642             JdbcSchema.Table.Column.Usage aggUsage =
643                 aggColumn.newUsage(JdbcSchema.UsageType.LEVEL);
644             // Cache table and column for the above
645
// check
646
aggUsage.relation = hierarchyUsage.getJoinTable();
647             aggUsage.joinExp = hierarchyUsage.getJoinExp();
648             aggUsage.levelColumnName = levelColumnName;
649
650             aggUsage.setSymbolicName(symbolicName);
651
652             String JavaDoc tableAlias;
653             if (aggUsage.joinExp instanceof MondrianDef.Column) {
654                 MondrianDef.Column mcolumn =
655                     (MondrianDef.Column) aggUsage.joinExp;
656                 tableAlias = mcolumn.table;
657             } else {
658                 tableAlias = aggUsage.relation.getAlias();
659             }
660
661
662             RolapStar.Table factTable = star.getFactTable();
663             RolapStar.Table descTable = factTable.findDescendant(tableAlias);
664
665             if (descTable == null) {
666                 // TODO: what to do here???
667
StringBuilder JavaDoc buf = new StringBuilder JavaDoc(256);
668                 buf.append("descendant table is null for factTable=");
669                 buf.append(factTable.getAlias());
670                 buf.append(", tableAlias=");
671                 buf.append(tableAlias);
672                 msgRecorder.reportError(buf.toString());
673
674                 returnValue = false;
675
676                 msgRecorder.throwRTException();
677             }
678
679             RolapStar.Column rc = descTable.lookupColumn(factColumnName);
680
681             if (rc == null) {
682                 rc = lookupInChildren(descTable, factColumnName);
683
684             }
685             if (rc == null) {
686                 StringBuilder JavaDoc buf = new StringBuilder JavaDoc(256);
687                 buf.append("Rolap.Column not found (null) for tableAlias=");
688                 buf.append(tableAlias);
689                 buf.append(", factColumnName=");
690                 buf.append(factColumnName);
691                 buf.append(", levelColumnName=");
692                 buf.append(levelColumnName);
693                 buf.append(", symbolicName=");
694                 buf.append(symbolicName);
695                 msgRecorder.reportError(buf.toString());
696
697                 returnValue = false;
698
699                 msgRecorder.throwRTException();
700             } else {
701                 aggUsage.rColumn = rc;
702             }
703         }
704         } finally {
705             msgRecorder.popContextName();
706         }
707     }
708
709     protected RolapStar.Column lookupInChildren(
710         final RolapStar.Table table,
711         final String JavaDoc factColumnName)
712     {
713         // This can happen if we are looking at a collapsed dimension
714
// table, and the collapsed dimension in question in the
715
// fact table is a snowflake (not just a star), so we
716
// must look deeper...
717
for (RolapStar.Table child : table.getChildren()) {
718             RolapStar.Column rc = child.lookupColumn(factColumnName);
719             if (rc != null) {
720                 return rc;
721             } else {
722                 rc = lookupInChildren(child, factColumnName);
723                 if (rc != null) {
724                     return rc;
725                 }
726             }
727         }
728         return null;
729     }
730
731
732     // Question: what if foreign key is seen, but there are also level
733
// columns - is this at least is a warning.
734

735
736     /**
737      * If everything is ok, issue warning for each aggTable column
738      * that has not been identified as a FACT_COLUMN, MEASURE_COLUMN or
739      * LEVEL_COLUMN.
740      */

741     protected void checkUnusedColumns() {
742         msgRecorder.pushContextName("Recognizer.checkUnusedColumns");
743         for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) {
744             if (! aggColumn.hasUsage()) {
745
746                 String JavaDoc msg = mres.AggUnknownColumn.str(
747                     aggTable.getName(),
748                     dbFactTable.getName(),
749                     aggColumn.getName()
750                 );
751                 // This is a fatal error for explicit recognizer
752
/*
753                  if (this instanceof ExplicitRecognizer) {
754                     msgRecorder.reportError(msg);
755                 } else {
756                     msgRecorder.reportWarning(msg);
757                 }
758
759                 Make this just a warning
760 */

761                 msgRecorder.reportWarning(msg);
762             }
763         }
764         msgRecorder.popContextName();
765     }
766
767     /**
768      * Figure out what aggregator should be associated with a column usage.
769      * Generally, this aggregator is simply the RolapAggregator returned by
770      * calling the getRollup() method of the fact table column's
771      * RolapAggregator. But in the case that the fact table column's
772      * RolapAggregator is the "Avg" aggregator, then the special
773      * RolapAggregator.AvgFromSum is used.
774      * <p>
775      * Note: this code assumes that the aggregate table does not have an
776      * explicit average aggregation column.
777      *
778      * @param aggUsage
779      * @param factAgg
780      */

781     protected RolapAggregator convertAggregator(
782             final JdbcSchema.Table.Column.Usage aggUsage,
783             final RolapAggregator factAgg) {
784
785         // NOTE: This assumes that the aggregate table does not have an explicit
786
// average column.
787
if (factAgg == RolapAggregator.Avg) {
788             String JavaDoc columnExpr = getFactCountExpr(aggUsage);
789             return new RolapAggregator.AvgFromSum(columnExpr);
790         } else if (factAgg == RolapAggregator.DistinctCount) {
791             //return RolapAggregator.Count;
792
return RolapAggregator.DistinctCount;
793         } else {
794             return factAgg;
795         }
796     }
797
798     /**
799      * The method chooses a special aggregator for the aggregate table column's
800      * usage.
801      * <pre>
802      * If the fact table column's aggregator was "Avg":
803      * then if the sibling aggregator was "Avg":
804      * the new aggregator is RolapAggregator.AvgFromAvg
805      * else if the sibling aggregator was "Sum":
806      * the new aggregator is RolapAggregator.AvgFromSum
807      * else if the fact table column's aggregator was "Sum":
808      * if the sibling aggregator was "Avg":
809      * the new aggregator is RolapAggregator.SumFromAvg
810      * </pre>
811      * Note that there is no SumFromSum since that is not a special case
812      * requiring a special aggregator.
813      * <p>
814      * if no new aggregator was selected, then the fact table's aggregator
815      * rollup aggregator is used.
816      *
817      * @param aggUsage
818      * @param factAgg
819      * @param siblingAgg
820      */

821     protected RolapAggregator convertAggregator(
822             final JdbcSchema.Table.Column.Usage aggUsage,
823             final RolapAggregator factAgg,
824             final RolapAggregator siblingAgg) {
825
826         msgRecorder.pushContextName("Recognizer.convertAggregator");
827         RolapAggregator rollupAgg = null;
828
829         String JavaDoc columnExpr = getFactCountExpr(aggUsage);
830         if (factAgg == RolapAggregator.Avg) {
831             if (siblingAgg == RolapAggregator.Avg) {
832                 rollupAgg = new RolapAggregator.AvgFromAvg(columnExpr);
833             } else if (siblingAgg == RolapAggregator.Sum) {
834                 rollupAgg = new RolapAggregator.AvgFromSum(columnExpr);
835             }
836         } else if (factAgg == RolapAggregator.Sum) {
837             if (siblingAgg == RolapAggregator.Avg) {
838                 rollupAgg = new RolapAggregator.SumFromAvg(columnExpr);
839             } else if (siblingAgg instanceof RolapAggregator.AvgFromAvg) {
840                 // needed for BUG_1541077.testTotalAmount
841
rollupAgg = new RolapAggregator.SumFromAvg(columnExpr);
842             }
843         }
844
845         if (rollupAgg == null) {
846             rollupAgg = (RolapAggregator) factAgg.getRollup();
847         }
848
849         if (rollupAgg == null) {
850             String JavaDoc msg = mres.NoAggregatorFound.str(
851                 aggUsage.getSymbolicName(),
852                 factAgg.getName(),
853                 siblingAgg.getName());
854             msgRecorder.reportError(msg);
855         }
856
857         msgRecorder.popContextName();
858         return rollupAgg;
859     }
860
861     /**
862      * Given an aggregate table column usage, find the column name of the
863      * table's fact count column usage.
864      *
865      * @param aggUsage
866      * @return
867      */

868     private String JavaDoc getFactCountExpr(final JdbcSchema.Table.Column.Usage aggUsage) {
869         // get the fact count column name.
870
JdbcSchema.Table aggTable = aggUsage.getColumn().getTable();
871
872         // iterator over fact count usages - in the end there can be only one!!
873
Iterator<JdbcSchema.Table.Column.Usage> it =
874             aggTable.getColumnUsages(JdbcSchema.UsageType.FACT_COUNT);
875         it.hasNext();
876         JdbcSchema.Table.Column.Usage usage = it.next();
877
878         // get the columns name
879
String JavaDoc factCountColumnName = usage.getColumn().getName();
880         String JavaDoc tableName = aggTable.getName();
881
882         // we want the fact count expression
883
MondrianDef.Column column =
884             new MondrianDef.Column(tableName, factCountColumnName);
885         SqlQuery sqlQuery = star.getSqlQuery();
886         return column.getExpression(sqlQuery);
887     }
888
889     /**
890      * Finds all cubes that use this fact table.
891      */

892     protected List<RolapCube> findCubes() {
893         String JavaDoc name = dbFactTable.getName();
894
895         List<RolapCube> list = new ArrayList<RolapCube>();
896         RolapSchema schema = star.getSchema();
897         for (RolapCube cube : schema.getCubeList()) {
898             if (cube.isVirtual()) {
899                 continue;
900             }
901             RolapStar cubeStar = cube.getStar();
902             String JavaDoc factTableName = cubeStar.getFactTable().getAlias();
903             if (name.equals(factTableName)) {
904                 list.add(cube);
905             }
906         }
907         return list;
908     }
909
910     /**
911      * Given a {@link mondrian.olap.MondrianDef.Expression}, returns
912      * the associated column name.
913      *
914      * <p>Note: if the {@link mondrian.olap.MondrianDef.Expression} is
915      * not a {@link mondrian.olap.MondrianDef.Column} or {@link
916      * mondrian.olap.MondrianDef.KeyExpression}, returns null. This
917      * will result in an error.
918      */

919     protected String JavaDoc getColumnName(MondrianDef.Expression expr) {
920         msgRecorder.pushContextName("Recognizer.getColumnName");
921
922         try {
923             if (expr instanceof MondrianDef.Column) {
924                 MondrianDef.Column column = (MondrianDef.Column) expr;
925                 return column.getColumnName();
926             } else if (expr instanceof MondrianDef.KeyExpression) {
927                 MondrianDef.KeyExpression key = (MondrianDef.KeyExpression) expr;
928                 return key.toString();
929             }
930
931             String JavaDoc msg = mres.NoColumnNameFromExpression.str(
932                 expr.toString());
933             msgRecorder.reportError(msg);
934
935             return null;
936
937         } finally {
938             msgRecorder.popContextName();
939         }
940     }
941 }
942
943 // End Recognizer.java
944
Popular Tags