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-2012 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap.aggmatcher; 012 013import mondrian.olap.*; 014import mondrian.recorder.MessageRecorder; 015import mondrian.rolap.*; 016import mondrian.rolap.aggmatcher.JdbcSchema.Table.Column; 017import mondrian.util.Pair; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Comparator; 022import java.util.Iterator; 023import java.util.List; 024 025/** 026 * This is the Recognizer for the aggregate table descriptions that appear in 027 * the catalog schema files; the user explicitly defines the aggregate. 028 * 029 * @author Richard M. Emberson 030 */ 031class ExplicitRecognizer extends Recognizer { 032 private ExplicitRules.TableDef tableDef; 033 private RolapCube cube; 034 035 ExplicitRecognizer( 036 final ExplicitRules.TableDef tableDef, 037 final RolapStar star, 038 RolapCube cube, 039 final JdbcSchema.Table dbFactTable, 040 final JdbcSchema.Table aggTable, 041 final MessageRecorder msgRecorder) 042 { 043 super(star, dbFactTable, aggTable, msgRecorder); 044 this.tableDef = tableDef; 045 this.cube = cube; 046 } 047 048 /** 049 * Get the ExplicitRules.TableDef associated with this instance. 050 */ 051 protected ExplicitRules.TableDef getTableDef() { 052 return tableDef; 053 } 054 055 /** 056 * Get the Matcher to be used to match columns to be ignored. 057 */ 058 protected Recognizer.Matcher getIgnoreMatcher() { 059 return getTableDef().getIgnoreMatcher(); 060 } 061 062 /** 063 * Get the Matcher to be used to match the column which is the fact count 064 * column. 065 */ 066 protected Recognizer.Matcher getFactCountMatcher() { 067 return getTableDef().getFactCountMatcher(); 068 } 069 070 /** 071 * Make the measures for this aggregate table. 072 * <p> 073 * First, iterate through all of the columns in the table. 074 * For each column, iterate through all of the tableDef measures, the 075 * explicit definitions of a measure. 076 * If the table's column name matches the column name in the measure 077 * definition, then make a measure. 078 * Next, look through all of the fact table column usage measures. 079 * For each such measure usage that has a sibling foreign key usage 080 * see if the tableDef has a foreign key defined with the same name. 081 * If so, then, for free, we can make a measure for the aggregate using 082 * its foreign key. 083 * <p> 084 * 085 * @return number of measures created. 086 */ 087 protected int checkMeasures() { 088 msgRecorder.pushContextName("ExplicitRecognizer.checkMeasures"); 089 try { 090 int measureColumnCounts = 0; 091 // Look at each aggregate table column. For each measure defined, 092 // see if the measure's column name equals the column's name. 093 // If so, make the aggregate measure usage for that column. 094 for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { 095 // if marked as ignore, then do not consider 096 if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { 097 continue; 098 } 099 100 String aggColumnName = aggColumn.getName(); 101 102 for (ExplicitRules.TableDef.Measure measure 103 : getTableDef().getMeasures()) 104 { 105 // Column name match is case insensitive 106 if (measure.getColumnName().equalsIgnoreCase(aggColumnName)) 107 { 108 String name = measure.getName(); 109 List<Id.Segment> parts = Util.parseIdentifier(name); 110 Id.Segment nameLast = Util.last(parts); 111 112 RolapStar.Measure m = null; 113 if (nameLast instanceof Id.NameSegment) { 114 m = star.getFactTable().lookupMeasureByName( 115 cube.getName(), 116 ((Id.NameSegment) nameLast).name); 117 } 118 RolapAggregator agg = null; 119 if (m != null) { 120 agg = m.getAggregator(); 121 } 122 // Ok, got a match, so now make a measure 123 makeMeasure(measure, agg, aggColumn); 124 measureColumnCounts++; 125 } 126 } 127 } 128 // Ok, now look at all of the fact table columns with measure usage 129 // that have a sibling foreign key usage. These can be automagically 130 // generated for the aggregate table as long as it still has the 131 // foreign key. 132 for (Iterator<JdbcSchema.Table.Column.Usage> it = 133 dbFactTable.getColumnUsages(JdbcSchema.UsageType.MEASURE); 134 it.hasNext();) 135 { 136 JdbcSchema.Table.Column.Usage factUsage = it.next(); 137 JdbcSchema.Table.Column factColumn = factUsage.getColumn(); 138 139 if (factColumn.hasUsage(JdbcSchema.UsageType.FOREIGN_KEY)) { 140 // What we've got here is a measure based upon a foreign key 141 String aggFK = 142 getTableDef().getAggregateFK(factColumn.getName()); 143 // OK, not a lost dimension 144 if (aggFK != null) { 145 JdbcSchema.Table.Column aggColumn = 146 aggTable.getColumn(aggFK); 147 148 // Column name match is case insensitive 149 if (aggColumn == null) { 150 aggColumn = aggTable.getColumn(aggFK.toLowerCase()); 151 } 152 if (aggColumn == null) { 153 aggColumn = aggTable.getColumn(aggFK.toUpperCase()); 154 } 155 156 if (aggColumn != null) { 157 makeMeasure(factUsage, aggColumn); 158 measureColumnCounts++; 159 } 160 } 161 } 162 } 163 return measureColumnCounts; 164 } finally { 165 msgRecorder.popContextName(); 166 } 167 } 168 169 /** 170 * Make a measure. This makes a measure usage using the Aggregator found in 171 * the RolapStar.Measure associated with the ExplicitRules.TableDef.Measure. 172 */ 173 protected void makeMeasure( 174 final ExplicitRules.TableDef.Measure measure, 175 RolapAggregator factAgg, 176 final JdbcSchema.Table.Column aggColumn) 177 { 178 RolapStar.Measure rm = measure.getRolapStarMeasure(); 179 180 JdbcSchema.Table.Column.Usage aggUsage = 181 aggColumn.newUsage(JdbcSchema.UsageType.MEASURE); 182 183 aggUsage.setSymbolicName(measure.getSymbolicName()); 184 RolapAggregator ra = (factAgg == null) 185 ? convertAggregator(aggUsage, rm.getAggregator()) 186 : convertAggregator(aggUsage, factAgg, rm.getAggregator()); 187 aggUsage.setAggregator(ra); 188 189 aggUsage.rMeasure = rm; 190 } 191 192 /** 193 * Creates a foreign key usage. 194 * 195 * <p> First the column name of the fact usage which is a foreign key is 196 * used to search for a foreign key definition in the 197 * ExplicitRules.tableDef. If not found, thats ok, it is just a lost 198 * dimension. If found, look for a column in the aggregate table with that 199 * name and make a foreign key usage. 200 */ 201 protected int matchForeignKey( 202 final JdbcSchema.Table.Column.Usage factUsage) 203 { 204 JdbcSchema.Table.Column factColumn = factUsage.getColumn(); 205 String aggFK = getTableDef().getAggregateFK(factColumn.getName()); 206 207 // OK, a lost dimension 208 if (aggFK == null) { 209 return 0; 210 } 211 212 int matchCount = 0; 213 for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { 214 // if marked as ignore, then do not consider 215 if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { 216 continue; 217 } 218 219 if (aggFK.equals(aggColumn.getName())) { 220 makeForeignKey(factUsage, aggColumn, aggFK); 221 matchCount++; 222 } 223 } 224 return matchCount; 225 } 226 227 /** 228 * Creates a level usage. A level usage is a column that is used in a 229 * collapsed dimension aggregate table. 230 * 231 * <p> First, iterate through the ExplicitRules.TableDef's level 232 * definitions for one with a name equal to the RolapLevel unique name, 233 * i.e., [Time].[Quarter]. Now, using the level's column name, search 234 * through the aggregate table's columns for one with that name and make a 235 * level usage for the column. 236 */ 237 protected void matchLevels( 238 final Hierarchy hierarchy, 239 final HierarchyUsage hierarchyUsage) 240 { 241 msgRecorder.pushContextName("ExplicitRecognizer.matchLevel"); 242 try { 243 // Try to match a Level's name against the RolapLevel 244 // unique name. 245 List<Pair<RolapLevel, JdbcSchema.Table.Column>> levelMatches = 246 new ArrayList<Pair<RolapLevel, JdbcSchema.Table.Column>>(); 247 List<ExplicitRules.TableDef.Level> aggLevels = 248 new ArrayList<ExplicitRules.TableDef.Level>(); 249 level_loop: 250 for (Level hLevel : hierarchy.getLevels()) { 251 if (hLevel.isAll()) { 252 continue; 253 } 254 final RolapLevel rLevel = (RolapLevel) hLevel; 255 String levelUniqueName = rLevel.getUniqueName(); 256 for (ExplicitRules.TableDef.Level level 257 : getTableDef().getLevels()) 258 { 259 if (level.getName().equals(levelUniqueName)) { 260 // Now can we find a column in the aggTable 261 // that matches the Level's column 262 final String columnName = level.getColumnName(); 263 for (JdbcSchema.Table.Column aggColumn 264 : aggTable.getColumns()) 265 { 266 if (aggColumn.getName() 267 .equalsIgnoreCase(columnName)) 268 { 269 levelMatches.add( 270 new Pair<RolapLevel, 271 JdbcSchema.Table.Column>( 272 rLevel, aggColumn)); 273 aggLevels.add(level); 274 continue level_loop; 275 } 276 } 277 } 278 } 279 } 280 if (levelMatches.size() == 0) { 281 return; 282 } 283 // Sort the matches by level depth. 284 Collections.sort( 285 levelMatches, 286 new Comparator<Pair<RolapLevel, JdbcSchema.Table.Column>>() { 287 public int compare( 288 Pair<RolapLevel, Column> o1, 289 Pair<RolapLevel, Column> o2) 290 { 291 return Util.compareIntegers( 292 o1.left.getDepth(), 293 o2.left.getDepth()); 294 } 295 }); 296 Collections.sort( 297 aggLevels, 298 new Comparator<ExplicitRules.TableDef.Level>() { 299 public int compare( 300 mondrian.rolap.aggmatcher 301 .ExplicitRules.TableDef.Level o1, 302 mondrian.rolap.aggmatcher 303 .ExplicitRules.TableDef.Level o2) 304 { 305 return Util.compareIntegers( 306 o1.getRolapLevel().getDepth(), 307 o2.getRolapLevel().getDepth()); 308 } 309 }); 310 // Validate by iterating. 311 boolean forceCollapse = false; 312 for (Pair<RolapLevel, JdbcSchema.Table.Column> pair 313 : levelMatches) 314 { 315 // Fail if the level is not the first match 316 // but the one before is not its parent. 317 if (levelMatches.indexOf(pair) > 0 318 && pair.left.getDepth() - 1 319 != levelMatches.get( 320 levelMatches.indexOf(pair) - 1).left.getDepth()) 321 { 322 msgRecorder.reportError( 323 "The aggregate table " 324 + aggTable.getName() 325 + " contains the column " 326 + pair.right.getName() 327 + " which maps to the level " 328 + pair.left.getUniqueName() 329 + " but its parent level is not part of that aggregation."); 330 } 331 // Warn if this level is marked as non-collapsed but the level 332 // above it is present in this agg table. 333 if (levelMatches.indexOf(pair) > 0 334 && !aggLevels.get(levelMatches.indexOf(pair)).isCollapsed()) 335 { 336 forceCollapse = true; 337 msgRecorder.reportWarning( 338 "The aggregate table " + aggTable.getName() 339 + " contains the column " + pair.right.getName() 340 + " which maps to the level " 341 + pair.left.getUniqueName() 342 + " and is marked as non-collapsed, but its parent column is already present."); 343 } 344 // Fail if the level is the first, it isn't at the top, 345 // but it is marked as collapsed. 346 if (levelMatches.indexOf(pair) == 0 347 && pair.left.getDepth() > 1 348 && aggLevels.get(levelMatches.indexOf(pair)).isCollapsed()) 349 { 350 msgRecorder.reportError( 351 "The aggregate table " 352 + aggTable.getName() 353 + " contains the column " 354 + pair.right.getName() 355 + " which maps to the level " 356 + pair.left.getUniqueName() 357 + " but its parent level is not part of that aggregation and this level is marked as collapsed."); 358 } 359 // Fail if the level is non-collapsed but its members 360 // are not unique. 361 if (!aggLevels.get( 362 levelMatches.indexOf(pair)).isCollapsed() 363 && !pair.left.isUnique()) 364 { 365 msgRecorder.reportError( 366 "The aggregate table " 367 + aggTable.getName() 368 + " contains the column " 369 + pair.right.getName() 370 + " which maps to the level " 371 + pair.left.getUniqueName() 372 + " but that level doesn't have unique members and this level is marked as non collapsed."); 373 } 374 } 375 if (msgRecorder.hasErrors()) { 376 return; 377 } 378 // All checks out. Let's create the levels. 379 for (Pair<RolapLevel, JdbcSchema.Table.Column> pair 380 : levelMatches) 381 { 382 makeLevel( 383 pair.right, 384 hierarchy, 385 hierarchyUsage, 386 getColumnName(pair.left.getKeyExp()), 387 aggLevels.get(levelMatches.indexOf(pair)).getColumnName(), 388 pair.left.getName(), 389 forceCollapse 390 ? true 391 : aggLevels.get(levelMatches.indexOf(pair)) 392 .isCollapsed(), 393 pair.left); 394 } 395 } finally { 396 msgRecorder.popContextName(); 397 } 398 } 399} 400 401// End ExplicitRecognizer.java