001/* 002// This software is subject to the terms of the Eclipse Public License v1.0 003// Agreement, available at the following URL: 004// http://www.eclipse.org/legal/epl-v10.html. 005// You must accept the terms of that agreement to use this software. 006// 007// Copyright (C) 2005-2005 Julian Hyde 008// Copyright (C) 2005-2009 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap.aggmatcher; 012 013import mondrian.olap.Hierarchy; 014import mondrian.olap.Level; 015import mondrian.recorder.MessageRecorder; 016import mondrian.resource.MondrianResource; 017import mondrian.rolap.*; 018import mondrian.rolap.aggmatcher.JdbcSchema.Table.Column; 019import mondrian.util.Pair; 020 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.Comparator; 024import java.util.Iterator; 025import java.util.List; 026 027/** 028 * This is the default Recognizer. It uses the rules found in the file 029 * DefaultRules.xml to find aggregate tables and there columns. 030 * 031 * @author Richard M. Emberson 032 */ 033class DefaultRecognizer extends Recognizer { 034 035 private static final MondrianResource mres = MondrianResource.instance(); 036 037 private final DefaultRules aggDefault; 038 039 DefaultRecognizer( 040 final DefaultRules aggDefault, 041 final RolapStar star, 042 final JdbcSchema.Table dbFactTable, 043 final JdbcSchema.Table aggTable, 044 final MessageRecorder msgRecorder) 045 { 046 super(star, dbFactTable, aggTable, msgRecorder); 047 this.aggDefault = aggDefault; 048 } 049 050 /** 051 * Get the DefaultRules instance associated with this object. 052 */ 053 DefaultRules getRules() { 054 return aggDefault; 055 } 056 057 /** 058 * Get the Matcher to be used to match columns to be ignored. 059 */ 060 protected Recognizer.Matcher getIgnoreMatcher() { 061 return getRules().getIgnoreMatcher(); 062 } 063 064 /** 065 * Get the Matcher to be used to match the column which is the fact count 066 * column. 067 */ 068 protected Recognizer.Matcher getFactCountMatcher() { 069 return getRules().getFactCountMatcher(); 070 } 071 072 /** 073 * Get the Match used to identify columns that are measures. 074 */ 075 protected Recognizer.Matcher getMeasureMatcher( 076 JdbcSchema.Table.Column.Usage factUsage) 077 { 078 String measureName = factUsage.getSymbolicName(); 079 String measureColumnName = factUsage.getColumn().getName(); 080 String aggregateName = factUsage.getAggregator().getName(); 081 082 return getRules().getMeasureMatcher( 083 measureName, 084 measureColumnName, 085 aggregateName); 086 } 087 088 /** 089 * Create measures for an aggregate table. 090 * <p> 091 * First, iterator through all fact table measure usages. 092 * Create a Matcher for each such usage. 093 * Iterate through all aggregate table columns. 094 * For each column that matches create a measure usage. 095 * <p> 096 * Per fact table measure usage, at most only one aggregate measure should 097 * be created. 098 * 099 * @return number of measures created. 100 */ 101 protected int checkMeasures() { 102 msgRecorder.pushContextName("DefaultRecognizer.checkMeasures"); 103 104 try { 105 int measureCountCount = 0; 106 107 for (Iterator<JdbcSchema.Table.Column.Usage> it = 108 dbFactTable.getColumnUsages(JdbcSchema.UsageType.MEASURE); 109 it.hasNext();) 110 { 111 JdbcSchema.Table.Column.Usage factUsage = it.next(); 112 113 Matcher matcher = getMeasureMatcher(factUsage); 114 115 int matchCount = 0; 116 for (JdbcSchema.Table.Column aggColumn 117 : aggTable.getColumns()) 118 { 119 // if marked as ignore, then do not consider 120 if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { 121 continue; 122 } 123 124 if (matcher.matches(aggColumn.getName())) { 125 makeMeasure(factUsage, aggColumn); 126 127 measureCountCount++; 128 matchCount++; 129 } 130 } 131 132 if (matchCount > 1) { 133 String msg = mres.AggMultipleMatchingMeasure.str( 134 msgRecorder.getContext(), 135 aggTable.getName(), 136 dbFactTable.getName(), 137 matchCount, 138 factUsage.getSymbolicName(), 139 factUsage.getColumn().getName(), 140 factUsage.getAggregator().getName()); 141 msgRecorder.reportError(msg); 142 143 returnValue = false; 144 } 145 } 146 return measureCountCount; 147 } finally { 148 msgRecorder.popContextName(); 149 } 150 } 151 152 /** 153 * This creates a foreign key usage. 154 * 155 * <p>Using the foreign key Matcher with the fact usage's column name the 156 * aggregate table's columns are searched for one that matches. For each 157 * that matches a foreign key usage is created (thought if more than one is 158 * created its is an error which is handled in the calling code. 159 */ 160 protected int matchForeignKey(JdbcSchema.Table.Column.Usage factUsage) { 161 JdbcSchema.Table.Column factColumn = factUsage.getColumn(); 162 163 // search to see if any of the aggTable's columns match 164 Recognizer.Matcher matcher = 165 getRules().getForeignKeyMatcher(factColumn.getName()); 166 167 int matchCount = 0; 168 for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { 169 // if marked as ignore, then do not consider 170 if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { 171 continue; 172 } 173 174 if (matcher.matches(aggColumn.getName())) { 175 makeForeignKey(factUsage, aggColumn, null); 176 matchCount++; 177 } 178 } 179 return matchCount; 180 } 181 182 /** 183 * Create level usages. 184 * 185 * <p> A Matcher is created using the Hierarchy's name, the RolapLevel 186 * name, and the column name associated with the RolapLevel's key 187 * expression. The aggregate table columns are search for the first match 188 * and, if found, a level usage is created for that column. 189 */ 190 protected void matchLevels( 191 final Hierarchy hierarchy, 192 final HierarchyUsage hierarchyUsage) 193 { 194 msgRecorder.pushContextName("DefaultRecognizer.matchLevel"); 195 try { 196 List<Pair<RolapLevel, JdbcSchema.Table.Column>> levelMatches = 197 new ArrayList<Pair<RolapLevel, JdbcSchema.Table.Column>>(); 198 level_loop: 199 for (Level level : hierarchy.getLevels()) { 200 if (level.isAll()) { 201 continue; 202 } 203 final RolapLevel rLevel = (RolapLevel) level; 204 205 String usagePrefix = hierarchyUsage.getUsagePrefix(); 206 String hierName = hierarchy.getName(); 207 String levelName = rLevel.getName(); 208 String levelColumnName = getColumnName(rLevel.getKeyExp()); 209 210 Recognizer.Matcher matcher = getRules().getLevelMatcher( 211 usagePrefix, hierName, levelName, levelColumnName); 212 213 for (JdbcSchema.Table.Column aggColumn 214 : aggTable.getColumns()) 215 { 216 if (matcher.matches(aggColumn.getName())) { 217 levelMatches.add( 218 new Pair<RolapLevel, 219 JdbcSchema.Table.Column>( 220 rLevel, aggColumn)); 221 continue level_loop; 222 } 223 } 224 } 225 if (levelMatches.size() == 0) { 226 return; 227 } 228 // Sort the matches by level depth. 229 Collections.sort( 230 levelMatches, 231 new Comparator<Pair<RolapLevel, JdbcSchema.Table.Column>>() { 232 public int compare( 233 Pair<RolapLevel, Column> o1, 234 Pair<RolapLevel, Column> o2) 235 { 236 return 237 Integer.valueOf(o1.left.getDepth()).compareTo( 238 Integer.valueOf(o2.left.getDepth())); 239 } 240 }); 241 // Validate by iterating. 242 for (Pair<RolapLevel, JdbcSchema.Table.Column> pair 243 : levelMatches) 244 { 245 boolean collapsed = true; 246 if (levelMatches.indexOf(pair) == 0 247 && pair.left.getDepth() > 1) 248 { 249 collapsed = false; 250 } 251 // Fail if the level is not the first match 252 // but the one before is not its parent. 253 if (levelMatches.indexOf(pair) > 0 254 && pair.left.getDepth() - 1 255 != levelMatches.get( 256 levelMatches.indexOf(pair) - 1).left.getDepth()) 257 { 258 msgRecorder.reportError( 259 "The aggregate table " 260 + aggTable.getName() 261 + " contains the column " 262 + pair.right.getName() 263 + " which maps to the level " 264 + pair.left.getUniqueName() 265 + " but its parent level is not part of that aggregation."); 266 } 267 // Fail if the level is non-collapsed but its members 268 // are not unique. 269 if (!collapsed 270 && !pair.left.isUnique()) 271 { 272 msgRecorder.reportError( 273 "The aggregate table " 274 + aggTable.getName() 275 + " contains the column " 276 + pair.right.getName() 277 + " which maps to the level " 278 + pair.left.getUniqueName() 279 + " but that level doesn't have unique members and this level is marked as non collapsed."); 280 } 281 } 282 if (msgRecorder.hasErrors()) { 283 return; 284 } 285 // All checks out. Let's create the levels. 286 for (Pair<RolapLevel, JdbcSchema.Table.Column> pair 287 : levelMatches) 288 { 289 boolean collapsed = true; 290 if (levelMatches.indexOf(pair) == 0 291 && pair.left.getDepth() > 1) 292 { 293 collapsed = false; 294 } 295 makeLevel( 296 pair.right, 297 hierarchy, 298 hierarchyUsage, 299 pair.right.column.name, 300 getColumnName(pair.left.getKeyExp()), 301 pair.left.getName(), 302 collapsed, 303 pair.left); 304 } 305 } finally { 306 msgRecorder.popContextName(); 307 } 308 } 309} 310 311// End DefaultRecognizer.java