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-2011 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.rolap.aggmatcher; 012 013import mondrian.olap.MondrianDef; 014import mondrian.olap.MondrianException; 015import mondrian.olap.MondrianProperties; 016import mondrian.olap.Util; 017import mondrian.recorder.ListRecorder; 018import mondrian.recorder.MessageRecorder; 019import mondrian.recorder.RecorderException; 020import mondrian.resource.MondrianResource; 021import mondrian.rolap.RolapCube; 022import mondrian.rolap.RolapSchema; 023import mondrian.rolap.RolapStar; 024 025import org.apache.log4j.Logger; 026 027import javax.sql.DataSource; 028 029import java.sql.SQLException; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.List; 033 034/** 035 * Manages aggregate tables. 036 * 037 * <p>It is used as follows:<ul> 038 * <li>A {@link mondrian.rolap.RolapSchema} creates an {@link AggTableManager}, 039 * and stores it in a member variable to ensure that it is not 040 * garbage-collected. 041 * <li>The {@link mondrian.rolap.RolapSchema} calls {@link #initialize()}, 042 * which scans the JDBC catalog and identifies aggregate tables. 043 * <li>For each aggregate table, it creates an {@link AggStar} and calls 044 * {@link RolapStar#addAggStar(AggStar)}. 045 * 046 * @author Richard M. Emberson 047 */ 048public class AggTableManager { 049 private static final Logger LOGGER = 050 Logger.getLogger(AggTableManager.class); 051 052 private final RolapSchema schema; 053 054 private static final MondrianResource mres = MondrianResource.instance(); 055 056 public AggTableManager(final RolapSchema schema) { 057 this.schema = schema; 058 } 059 060 /** 061 * This should ONLY be called if the AggTableManager is no longer going 062 * to be used. In fact, it should only be called indirectly by its 063 * associated RolapSchema object. 064 */ 065 public void finalCleanUp() { 066 removeJdbcSchema(); 067 068 if (getLogger().isDebugEnabled()) { 069 getLogger().debug( 070 "AggTableManager.finalCleanUp: schema=" 071 + schema.getName()); 072 } 073 } 074 075 /** 076 * Get the Logger. 077 */ 078 public Logger getLogger() { 079 return LOGGER; 080 } 081 082 /** 083 * Initializes this object, loading all aggregate tables and associating 084 * them with {@link RolapStar}s. 085 * This method should only be called once. 086 */ 087 public void initialize() { 088 if (MondrianProperties.instance().ReadAggregates.get()) { 089 try { 090 loadRolapStarAggregates(); 091 } catch (SQLException ex) { 092 throw mres.AggLoadingError.ex(ex); 093 } 094 } 095 printResults(); 096 } 097 098 private void printResults() { 099/* 100 * This was too much information at the INFO level, compared to the 101 * rest of Mondrian 102 * 103 * if (getLogger().isInfoEnabled()) { 104 // print just Star table alias and AggStar table names 105 StringBuilder buf = new StringBuilder(1024); 106 buf.append(Util.nl); 107 for (Iterator it = getStars(); it.hasNext();) { 108 RolapStar star = (RolapStar) it.next(); 109 buf.append(star.getFactTable().getAlias()); 110 buf.append(Util.nl); 111 for (Iterator ait = star.getAggStars(); ait.hasNext();) { 112 AggStar aggStar = (AggStar) ait.next(); 113 buf.append(" "); 114 buf.append(aggStar.getFactTable().getName()); 115 buf.append(Util.nl); 116 } 117 } 118 getLogger().info(buf.toString()); 119 120 } else 121*/ 122 if (getLogger().isDebugEnabled()) { 123 // print everything, Star, subTables, AggStar and subTables 124 // could be a lot 125 StringBuilder buf = new StringBuilder(4096); 126 buf.append(Util.nl); 127 for (RolapStar star : getStars()) { 128 buf.append(star.toString()); 129 buf.append(Util.nl); 130 } 131 getLogger().debug(buf.toString()); 132 } 133 } 134 135 private JdbcSchema getJdbcSchema() { 136 DataSource dataSource = schema.getInternalConnection().getDataSource(); 137 138 // This actually just does a lookup or simple constructor invocation, 139 // its not expected to fail 140 return JdbcSchema.makeDB(dataSource); 141 } 142 143 /** 144 * Remove the possibly already loaded snapshot of what is in the database. 145 */ 146 private void removeJdbcSchema() { 147 DataSource dataSource = schema.getInternalConnection().getDataSource(); 148 JdbcSchema.removeDB(dataSource); 149 } 150 151 152 /** 153 * This method loads and/or reloads the aggregate tables. 154 * <p> 155 * NOTE: At this point all RolapStars have been made for this 156 * schema (except for dynamically added cubes which I am going 157 * to ignore for right now). So, All stars have their columns 158 * and their BitKeys can be generated. 159 * 160 * @throws SQLException 161 */ 162 private void loadRolapStarAggregates() throws SQLException { 163 ListRecorder msgRecorder = new ListRecorder(); 164 try { 165 DefaultRules rules = DefaultRules.getInstance(); 166 JdbcSchema db = getJdbcSchema(); 167 // if we don't synchronize this on the db object, 168 // we may end up getting a Concurrency exception due to 169 // calls to other instances of AggTableManager.finalCleanUp() 170 synchronized (db) { 171 // fix for MONDRIAN-496 172 // flush any existing usages of the jdbc schema, so we 173 // don't accidentally use another star's metadata 174 db.flushUsages(); 175 176 // loads tables, not their columns 177 db.load(); 178 179 loop: 180 for (RolapStar star : getStars()) { 181 // This removes any AggStars from any previous invocation of 182 // this method (if any) 183 star.prepareToLoadAggregates(); 184 185 List<ExplicitRules.Group> aggGroups = getAggGroups(star); 186 for (ExplicitRules.Group group : aggGroups) { 187 group.validate(msgRecorder); 188 } 189 190 String factTableName = star.getFactTable().getAlias(); 191 192 JdbcSchema.Table dbFactTable = db.getTable(factTableName); 193 if (dbFactTable == null) { 194 msgRecorder.reportWarning( 195 "No Table found for fact name=" 196 + factTableName); 197 continue loop; 198 } 199 200 // For each column in the dbFactTable, figure out it they 201 // are measure or foreign key columns 202 203 bindToStar(dbFactTable, star, msgRecorder); 204 String schema = dbFactTable.table.schema; 205 206 // Now look at all tables in the database and per table, 207 // first see if it is a match for an aggregate table for 208 // this fact table and second see if its columns match 209 // foreign key and level columns. 210 211 for (JdbcSchema.Table dbTable : db.getTables()) { 212 String name = dbTable.getName(); 213 214 // Do the catalog schema aggregate excludes, exclude 215 // this table name. 216 if (ExplicitRules.excludeTable(name, aggGroups)) { 217 continue; 218 } 219 220 // First see if there is an ExplicitRules match. If so, 221 // then if all of the columns match up, then make an 222 // AggStar. On the other hand, if there is no 223 // ExplicitRules match, see if there is a Default 224 // match. If so and if all the columns match up, then 225 // also make an AggStar. 226 ExplicitRules.TableDef tableDef = 227 ExplicitRules.getIncludeByTableDef(name, aggGroups); 228 229 boolean makeAggStar = false; 230 int approxRowCount = Integer.MIN_VALUE; 231 // Is it handled by the ExplicitRules 232 if (tableDef != null) { 233 // load columns 234 dbTable.load(); 235 makeAggStar = tableDef.columnsOK( 236 star, 237 dbFactTable, 238 dbTable, 239 msgRecorder); 240 approxRowCount = tableDef.getApproxRowCount(); 241 } 242 if (! makeAggStar) { 243 // Is it handled by the DefaultRules 244 if (rules.matchesTableName(factTableName, name)) { 245 // load columns 246 dbTable.load(); 247 makeAggStar = rules.columnsOK( 248 star, 249 dbFactTable, 250 dbTable, 251 msgRecorder); 252 } 253 } 254 255 if (makeAggStar) { 256 dbTable.setTableUsageType( 257 JdbcSchema.TableUsageType.AGG); 258 dbTable.table = new MondrianDef.Table( 259 schema, 260 name, 261 null, // null alias 262 null); // don't know about table hints 263 AggStar aggStar = AggStar.makeAggStar( 264 star, 265 dbTable, 266 msgRecorder, 267 approxRowCount); 268 if (aggStar.getSize() > 0) { 269 star.addAggStar(aggStar); 270 } else { 271 getLogger().warn( 272 mres.AggTableZeroSize.str( 273 aggStar.getFactTable().getName(), 274 factTableName)); 275 } 276 } 277 // Note: if the dbTable name matches but the columnsOK 278 // does not, then this is an error and the aggregate 279 // tables can not be loaded. 280 // We do not "reset" the column usages in the dbTable 281 // allowing it maybe to match another rule. 282 } 283 } 284 } 285 } catch (RecorderException ex) { 286 throw new MondrianException(ex); 287 } finally { 288 msgRecorder.logInfoMessage(getLogger()); 289 msgRecorder.logWarningMessage(getLogger()); 290 msgRecorder.logErrorMessage(getLogger()); 291 if (msgRecorder.hasErrors()) { 292 throw mres.AggLoadingExceededErrorCount.ex( 293 msgRecorder.getErrorCount()); 294 } 295 } 296 } 297 298 private Collection<RolapStar> getStars() { 299 return schema.getStars(); 300 } 301 302 /** 303 * Returns a list containing every 304 * {@link mondrian.rolap.aggmatcher.ExplicitRules.Group} in every 305 * cubes in a given {@link RolapStar}. 306 */ 307 protected List<ExplicitRules.Group> getAggGroups(RolapStar star) { 308 List<ExplicitRules.Group> aggGroups = 309 new ArrayList<ExplicitRules.Group>(); 310 for (RolapCube cube : schema.getCubesWithStar(star)) { 311 if (cube.hasAggGroup() && cube.getAggGroup().hasRules()) { 312 aggGroups.add(cube.getAggGroup()); 313 } 314 } 315 return aggGroups; 316 } 317 318 /** 319 * This method mines the RolapStar and annotes the JdbcSchema.Table 320 * dbFactTable by creating JdbcSchema.Table.Column.Usage instances. For 321 * example, a measure in the RolapStar becomes a measure usage for the 322 * column with the same name and a RolapStar foreign key column becomes a 323 * foreign key usage for the column with the same name. 324 * 325 * @param dbFactTable 326 * @param star 327 * @param msgRecorder 328 */ 329 void bindToStar( 330 final JdbcSchema.Table dbFactTable, 331 final RolapStar star, 332 final MessageRecorder msgRecorder) 333 throws SQLException 334 { 335 msgRecorder.pushContextName("AggTableManager.bindToStar"); 336 try { 337 // load columns 338 dbFactTable.load(); 339 340 dbFactTable.setTableUsageType(JdbcSchema.TableUsageType.FACT); 341 342 MondrianDef.RelationOrJoin relation = 343 star.getFactTable().getRelation(); 344 String schema = null; 345 MondrianDef.Hint[] tableHints = null; 346 if (relation instanceof MondrianDef.Table) { 347 schema = ((MondrianDef.Table) relation).schema; 348 tableHints = ((MondrianDef.Table) relation).tableHints; 349 } 350 String tableName = dbFactTable.getName(); 351 String alias = null; 352 dbFactTable.table = new MondrianDef.Table( 353 schema, 354 tableName, 355 alias, 356 tableHints); 357 358 for (JdbcSchema.Table.Column factColumn 359 : dbFactTable.getColumns()) 360 { 361 String cname = factColumn.getName(); 362 RolapStar.Column[] rcs = 363 star.getFactTable().lookupColumns(cname); 364 365 for (RolapStar.Column rc : rcs) { 366 // its a measure 367 if (rc instanceof RolapStar.Measure) { 368 RolapStar.Measure rm = (RolapStar.Measure) rc; 369 JdbcSchema.Table.Column.Usage usage = 370 factColumn.newUsage(JdbcSchema.UsageType.MEASURE); 371 usage.setSymbolicName(rm.getName()); 372 373 usage.setAggregator(rm.getAggregator()); 374 usage.rMeasure = rm; 375 } 376 } 377 378 // it still might be a foreign key 379 RolapStar.Table rTable = 380 star.getFactTable().findTableWithLeftJoinCondition(cname); 381 if (rTable != null) { 382 JdbcSchema.Table.Column.Usage usage = 383 factColumn.newUsage(JdbcSchema.UsageType.FOREIGN_KEY); 384 usage.setSymbolicName("FOREIGN_KEY"); 385 usage.rTable = rTable; 386 } else { 387 RolapStar.Column rColumn = 388 star.getFactTable().lookupColumn(cname); 389 if ((rColumn != null) 390 && !(rColumn instanceof RolapStar.Measure)) 391 { 392 // Ok, maybe its used in a non-shared dimension 393 // This is a column in the fact table which is 394 // (not necessarily) a measure but is also not 395 // a foreign key to an external dimension table. 396 JdbcSchema.Table.Column.Usage usage = 397 factColumn.newUsage( 398 JdbcSchema.UsageType.FOREIGN_KEY); 399 usage.setSymbolicName("FOREIGN_KEY"); 400 usage.rColumn = rColumn; 401 } 402 } 403 404 // warn if it has not been identified 405 if (!factColumn.hasUsage() && getLogger().isDebugEnabled()) { 406 getLogger().debug( 407 mres.UnknownFactTableColumn.str( 408 msgRecorder.getContext(), 409 dbFactTable.getName(), 410 factColumn.getName())); 411 } 412 } 413 } finally { 414 msgRecorder.popContextName(); 415 } 416 } 417} 418 419// End AggTableManager.java