001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/FastBatchingCellReader.java#64 $ 003 // This software is subject to the terms of the Common Public License 004 // Agreement, available at the following URL: 005 // http://www.opensource.org/licenses/cpl.html. 006 // Copyright (C) 2004-2008 Julian Hyde and others 007 // All Rights Reserved. 008 // You must accept the terms of that agreement to use this software. 009 */ 010 package mondrian.rolap; 011 012 import mondrian.olap.*; 013 import mondrian.rolap.agg.*; 014 import mondrian.rolap.aggmatcher.AggGen; 015 import mondrian.rolap.aggmatcher.AggStar; 016 import mondrian.rolap.sql.SqlQuery; 017 018 import org.apache.log4j.Logger; 019 import org.eigenbase.util.property.*; 020 import org.eigenbase.util.property.Property; 021 022 import java.util.*; 023 024 /** 025 * A <code>FastBatchingCellReader</code> doesn't really Read cells: when asked 026 * to look up the values of stored measures, it lies, and records the fact 027 * that the value was asked for. Later, we can look over the values which 028 * are required, fetch them in an efficient way, and re-run the evaluation 029 * with a real evaluator. 030 * 031 * <p>NOTE: When it doesn't know the answer, it lies by returning an error 032 * object. The calling code must be able to deal with that.</p> 033 * 034 * <p>This class tries to minimize the amount of storage needed to record the 035 * fact that a cell was requested.</p> 036 */ 037 public class FastBatchingCellReader implements CellReader { 038 039 private static final Logger LOGGER = 040 Logger.getLogger(FastBatchingCellReader.class); 041 042 /** 043 * This static variable controls the generation of aggregate table sql. 044 */ 045 private static boolean generateAggregateSql = 046 MondrianProperties.instance().GenerateAggregateSql.get(); 047 048 static { 049 // Trigger is used to lookup and change the value of the 050 // variable that controls generating aggregate table sql. 051 // Using a trigger means we don't have to look up the property eveytime. 052 MondrianProperties.instance().GenerateAggregateSql.addTrigger( 053 new TriggerBase(true) { 054 public void execute(Property property, String value) { 055 generateAggregateSql = property.booleanValue(); 056 } 057 }); 058 } 059 060 private final RolapCube cube; 061 private final Map<AggregationKey, Batch> batches; 062 063 /** 064 * Records the number of requests. The field is used for correctness: if 065 * the request count stays the same during an operation, you know that the 066 * FastBatchingCellReader has not told any lies during that operation, and 067 * therefore the result is true. The field is also useful for debugging. 068 */ 069 private int requestCount; 070 071 final AggregationManager aggMgr = AggregationManager.instance(); 072 073 private final RolapAggregationManager.PinSet pinnedSegments = 074 aggMgr.createPinSet(); 075 076 /** 077 * Indicates that the reader has given incorrect results. 078 */ 079 private boolean dirty; 080 081 public FastBatchingCellReader(RolapCube cube) { 082 assert cube != null; 083 this.cube = cube; 084 this.batches = new HashMap<AggregationKey, Batch>(); 085 } 086 087 public Object get(RolapEvaluator evaluator) { 088 final CellRequest request = 089 RolapAggregationManager.makeRequest(evaluator); 090 091 if (request == null || request.isUnsatisfiable()) { 092 return Util.nullValue; // request not satisfiable. 093 } 094 095 // Try to retrieve a cell and simultaneously pin the segment which 096 // contains it. 097 final Object o = aggMgr.getCellFromCache(request, pinnedSegments); 098 099 if (o == Boolean.TRUE) { 100 // Aggregation is being loaded. (todo: Use better value, or 101 // throw special exception) 102 return RolapUtil.valueNotReadyException; 103 } 104 if (o != null) { 105 return o; 106 } 107 // if there is no such cell, record that we need to fetch it, and 108 // return 'error' 109 recordCellRequest(request); 110 return RolapUtil.valueNotReadyException; 111 } 112 113 public int getMissCount() { 114 return requestCount; 115 } 116 117 public final void recordCellRequest(CellRequest request) { 118 if (request.isUnsatisfiable()) { 119 return; 120 } 121 ++requestCount; 122 123 final AggregationKey key = new AggregationKey(request); 124 Batch batch = batches.get(key); 125 if (batch == null) { 126 batch = new Batch(request); 127 batches.put(key, batch); 128 129 if (LOGGER.isDebugEnabled()) { 130 StringBuilder buf = new StringBuilder(100); 131 buf.append("FastBatchingCellReader: bitkey="); 132 buf.append(request.getConstrainedColumnsBitKey()); 133 buf.append(Util.nl); 134 135 final RolapStar.Column[] columns = 136 request.getConstrainedColumns(); 137 for (RolapStar.Column column : columns) { 138 buf.append(" "); 139 buf.append(column); 140 buf.append(Util.nl); 141 } 142 LOGGER.debug(buf.toString()); 143 } 144 } 145 batch.add(request); 146 } 147 148 /** 149 * Returns whether this reader has told a lie. This is the case if there 150 * are pending batches to load or if {@link #setDirty(boolean)} has been 151 * called. 152 */ 153 public boolean isDirty() { 154 return dirty || !batches.isEmpty(); 155 } 156 157 boolean loadAggregations() { 158 return loadAggregations(null); 159 } 160 161 /** 162 * Loads pending aggregations, if any. 163 * 164 * @param query the parent query object that initiated this 165 * call 166 * 167 * @return Whether any aggregations were loaded. 168 */ 169 boolean loadAggregations(Query query) { 170 final long t1 = System.currentTimeMillis(); 171 if (!isDirty()) { 172 return false; 173 } 174 175 // Sort the batches into deterministic order. 176 List<Batch> batchList = new ArrayList<Batch>(batches.values()); 177 Collections.sort(batchList, BatchComparator.instance); 178 if (shouldUseGroupingFunction()) { 179 LOGGER.debug("Using grouping sets"); 180 List<CompositeBatch> groupedBatches = groupBatches(batchList); 181 for (CompositeBatch batch : groupedBatches) { 182 loadAggregation(query, batch); 183 } 184 } else { 185 // Load batches in turn. 186 for (Batch batch : batchList) { 187 loadAggregation(query, batch); 188 } 189 } 190 191 batches.clear(); 192 dirty = false; 193 194 if (LOGGER.isDebugEnabled()) { 195 final long t2 = System.currentTimeMillis(); 196 LOGGER.debug("loadAggregation (millis): " + (t2 - t1)); 197 } 198 199 return true; 200 } 201 202 private void loadAggregation(Query query, Loadable batch) { 203 if (query != null) { 204 query.checkCancelOrTimeout(); 205 } 206 batch.loadAggregation(); 207 } 208 209 List<CompositeBatch> groupBatches(List<Batch> batchList) { 210 Map<AggregationKey, CompositeBatch> batchGroups = 211 new HashMap<AggregationKey, CompositeBatch>(); 212 for (int i = 0; i < batchList.size(); i++) { 213 for (int j = i + 1; j < batchList.size() && i < batchList.size();) { 214 FastBatchingCellReader.Batch iBatch = batchList.get(i); 215 FastBatchingCellReader.Batch jBatch = batchList.get(j); 216 if (iBatch.canBatch(jBatch)) { 217 batchList.remove(j); 218 addToCompositeBatch(batchGroups, iBatch, jBatch); 219 } else if (jBatch.canBatch(iBatch)) { 220 batchList.set(i, jBatch); 221 batchList.remove(j); 222 addToCompositeBatch(batchGroups, jBatch, iBatch); 223 j = i + 1; 224 } else { 225 j++; 226 } 227 } 228 } 229 230 wrapNonBatchedBatchesWithCompositeBatches(batchList, batchGroups); 231 final CompositeBatch[] compositeBatches = 232 batchGroups.values().toArray( 233 new CompositeBatch[batchGroups.size()]); 234 Arrays.sort(compositeBatches, CompositeBatchComparator.instance); 235 return Arrays.asList(compositeBatches); 236 } 237 238 private void wrapNonBatchedBatchesWithCompositeBatches( 239 List<Batch> batchList, 240 Map<AggregationKey, CompositeBatch> batchGroups) 241 { 242 for (Batch batch : batchList) { 243 if (batchGroups.get(batch.batchKey) == null) { 244 batchGroups.put(batch.batchKey, new CompositeBatch(batch)); 245 } 246 } 247 } 248 249 250 void addToCompositeBatch( 251 Map<AggregationKey, CompositeBatch> batchGroups, 252 Batch detailedBatch, 253 Batch summaryBatch) 254 { 255 CompositeBatch compositeBatch = batchGroups.get(detailedBatch.batchKey); 256 257 if (compositeBatch == null) { 258 compositeBatch = new CompositeBatch(detailedBatch); 259 batchGroups.put(detailedBatch.batchKey, compositeBatch); 260 } 261 262 FastBatchingCellReader.CompositeBatch compositeBatchOfSummaryBatch = 263 batchGroups.remove(summaryBatch.batchKey); 264 265 if (compositeBatchOfSummaryBatch != null) { 266 compositeBatch.merge(compositeBatchOfSummaryBatch); 267 } else { 268 compositeBatch.add(summaryBatch); 269 } 270 271 } 272 273 boolean shouldUseGroupingFunction() { 274 return MondrianProperties.instance().EnableGroupingSets.get() && 275 doesDBSupportGroupingSets(); 276 } 277 278 /** 279 * Uses Dialect to identify if grouping sets is supported by the 280 * database. 281 */ 282 boolean doesDBSupportGroupingSets() { 283 return getDialect().supportsGroupingSets(); 284 } 285 286 /** 287 * Returns the SQL dialect. Overridden in some unit tests. 288 * 289 * @return Dialect 290 */ 291 SqlQuery.Dialect getDialect() { 292 final RolapStar star = cube.getStar(); 293 if (star != null) { 294 return star.getSqlQueryDialect(); 295 } else { 296 return cube.getSchema().getDialect(); 297 } 298 } 299 300 /** 301 * Sets the flag indicating that the reader has told a lie. 302 */ 303 void setDirty(boolean dirty) { 304 this.dirty = dirty; 305 } 306 307 /** 308 * Set of Batches which can grouped together. 309 */ 310 class CompositeBatch implements Loadable { 311 /** Batch with most number of constraint columns */ 312 final Batch detailedBatch; 313 314 /** Batches whose data can be fetched using rollup on detailed batch */ 315 final List<Batch> summaryBatches = new ArrayList<Batch>(); 316 317 CompositeBatch(Batch detailedBatch) { 318 this.detailedBatch = detailedBatch; 319 } 320 321 void add(Batch summaryBatch) { 322 summaryBatches.add(summaryBatch); 323 } 324 325 void merge(CompositeBatch summaryBatch) { 326 summaryBatches.add(summaryBatch.detailedBatch); 327 summaryBatches.addAll(summaryBatch.summaryBatches); 328 } 329 330 public void loadAggregation() { 331 GroupingSetsCollector batchCollector = 332 new GroupingSetsCollector(true); 333 this.detailedBatch.loadAggregation(batchCollector); 334 335 for (Batch batch : summaryBatches) { 336 batch.loadAggregation(batchCollector); 337 } 338 339 getSegmentLoader().load( 340 batchCollector.getGroupingSets(), 341 pinnedSegments, 342 detailedBatch.batchKey.getCompoundPredicateList()); 343 } 344 345 SegmentLoader getSegmentLoader() { 346 return new SegmentLoader(); 347 } 348 } 349 350 private static final Logger BATCH_LOGGER = Logger.getLogger(Batch.class); 351 352 /** 353 * Encapsulates a common property of {@link Batch} and 354 * {@link CompositeBatch}, namely, that they can be asked to load their 355 * aggregations into the cache. 356 */ 357 interface Loadable { 358 void loadAggregation(); 359 } 360 361 class Batch implements Loadable { 362 // the CellRequest's constrained columns 363 final RolapStar.Column[] columns; 364 final List<RolapStar.Measure> measuresList = 365 new ArrayList<RolapStar.Measure>(); 366 final Set<StarColumnPredicate>[] valueSets; 367 final AggregationKey batchKey; 368 // string representation; for debug; set lazily in toString 369 private String string; 370 371 public Batch(CellRequest request) { 372 columns = request.getConstrainedColumns(); 373 valueSets = new HashSet[columns.length]; 374 for (int i = 0; i < valueSets.length; i++) { 375 valueSets[i] = new HashSet<StarColumnPredicate>(); 376 } 377 batchKey = new AggregationKey(request); 378 } 379 380 public String toString() { 381 if (string == null) { 382 final StringBuilder buf = new StringBuilder(); 383 buf.append("Batch {\n") 384 .append(" columns={").append(Arrays.asList(columns)) 385 .append("}\n") 386 .append(" measures={").append(measuresList).append("}\n") 387 .append(" valueSets={").append(Arrays.asList(valueSets)) 388 .append("}\n") 389 .append(" batchKey=").append(batchKey).append("}\n") 390 .append("}"); 391 string = buf.toString(); 392 } 393 return string; 394 } 395 396 public final void add(CellRequest request) { 397 final List<StarColumnPredicate> values = request.getValueList(); 398 for (int j = 0; j < columns.length; j++) { 399 valueSets[j].add(values.get(j)); 400 } 401 final RolapStar.Measure measure = request.getMeasure(); 402 if (!measuresList.contains(measure)) { 403 assert (measuresList.size() == 0) || 404 (measure.getStar() == 405 (measuresList.get(0)).getStar()): 406 "Measure must belong to same star as other measures"; 407 measuresList.add(measure); 408 } 409 } 410 411 /** 412 * Returns the RolapStar associated with the Batch's first Measure. 413 * 414 * <p>This method can only be called after the {@link #add} method has 415 * been called. 416 * 417 * @return the RolapStar associated with the Batch's first Measure 418 */ 419 private RolapStar getStar() { 420 RolapStar.Measure measure = measuresList.get(0); 421 return measure.getStar(); 422 } 423 424 public BitKey getConstrainedColumnsBitKey() { 425 return batchKey.getConstrainedColumnsBitKey(); 426 } 427 428 public final void loadAggregation() { 429 GroupingSetsCollector collectorWithGroupingSetsTurnedOff = 430 new GroupingSetsCollector(false); 431 loadAggregation(collectorWithGroupingSetsTurnedOff); 432 } 433 434 final void loadAggregation( 435 GroupingSetsCollector groupingSetsCollector) 436 { 437 if (generateAggregateSql) { 438 generateAggregateSql(); 439 } 440 final StarColumnPredicate[] predicates = initPredicates(); 441 final long t1 = System.currentTimeMillis(); 442 443 final AggregationManager aggmgr = AggregationManager.instance(); 444 445 // TODO: optimize key sets; drop a constraint if more than x% of 446 // the members are requested; whether we should get just the cells 447 // requested or expand to a n-cube 448 449 // If the database cannot execute "count(distinct ...)", split the 450 // distinct aggregations out. 451 final SqlQuery.Dialect dialect = getDialect(); 452 453 int distinctMeasureCount = getDistinctMeasureCount(measuresList); 454 boolean tooManyDistinctMeasures = 455 distinctMeasureCount > 0 && 456 !dialect.allowsCountDistinct() || 457 distinctMeasureCount > 1 && 458 !dialect.allowsMultipleCountDistinct(); 459 460 if (tooManyDistinctMeasures) { 461 doSpecialHandlingOfDistinctCountMeasures( 462 aggmgr, predicates, groupingSetsCollector); 463 } 464 465 // Load agg(distinct <SQL expression>) measures individually 466 // for DBs that does allow multiple distinct SQL measures. 467 if (!dialect.allowsMultipleDistinctSqlMeasures()) { 468 469 // Note that the intention was orignially to capture the 470 // subquery SQL measures and separate them out; However, 471 // without parsing the SQL string, Mondrian cannot distinguish 472 // between "col1" + "col2" and subquery. Here the measure list 473 // contains both types. 474 475 // See the test case testLoadDistinctSqlMeasure() in 476 // mondrian.rolap.FastBatchingCellReaderTest 477 478 List<RolapStar.Measure> distinctSqlMeasureList = 479 getDistinctSqlMeasures(measuresList); 480 for (RolapStar.Measure measure : distinctSqlMeasureList) { 481 RolapStar.Measure[] measures = {measure}; 482 aggmgr.loadAggregation( 483 measures, columns, 484 batchKey, 485 predicates, 486 pinnedSegments, groupingSetsCollector); 487 measuresList.remove(measure); 488 } 489 } 490 491 final int measureCount = measuresList.size(); 492 if (measureCount > 0) { 493 final RolapStar.Measure[] measures = 494 measuresList.toArray(new RolapStar.Measure[measureCount]); 495 aggmgr.loadAggregation( 496 measures, columns, 497 batchKey, 498 predicates, 499 pinnedSegments, groupingSetsCollector); 500 } 501 502 if (BATCH_LOGGER.isDebugEnabled()) { 503 final long t2 = System.currentTimeMillis(); 504 BATCH_LOGGER.debug( 505 "Batch.loadAggregation (millis) " + (t2 - t1)); 506 } 507 } 508 509 private void doSpecialHandlingOfDistinctCountMeasures( 510 AggregationManager aggmgr, 511 StarColumnPredicate[] predicates, 512 GroupingSetsCollector groupingSetsCollector) 513 { 514 while (true) { 515 // Scan for a measure based upon a distinct aggregation. 516 final RolapStar.Measure distinctMeasure = 517 getFirstDistinctMeasure(measuresList); 518 if (distinctMeasure == null) { 519 break; 520 } 521 final String expr = 522 distinctMeasure.getExpression().getGenericExpression(); 523 final List<RolapStar.Measure> distinctMeasuresList = 524 new ArrayList<RolapStar.Measure>(); 525 for (int i = 0; i < measuresList.size();) { 526 final RolapStar.Measure measure = measuresList.get(i); 527 if (measure.getAggregator().isDistinct() && 528 measure.getExpression().getGenericExpression(). 529 equals(expr)) { 530 measuresList.remove(i); 531 distinctMeasuresList.add(distinctMeasure); 532 } else { 533 i++; 534 } 535 } 536 537 // Load all the distinct measures based on the same expression 538 // together 539 final RolapStar.Measure[] measures = 540 distinctMeasuresList.toArray( 541 new RolapStar.Measure[distinctMeasuresList.size()]); 542 aggmgr.loadAggregation( 543 measures, columns, 544 batchKey, 545 predicates, 546 pinnedSegments, groupingSetsCollector); 547 } 548 } 549 550 private StarColumnPredicate[] initPredicates() { 551 StarColumnPredicate[] predicates = 552 new StarColumnPredicate[columns.length]; 553 for (int j = 0; j < columns.length; j++) { 554 Set<StarColumnPredicate> valueSet = valueSets[j]; 555 556 StarColumnPredicate predicate; 557 if (valueSet == null) { 558 predicate = LiteralStarPredicate.FALSE; 559 } else { 560 ValueColumnPredicate[] values = 561 valueSet.toArray( 562 new ValueColumnPredicate[valueSet.size()]); 563 // Sort array to achieve determinism in generated SQL. 564 Arrays.sort( 565 values, 566 ValueColumnConstraintComparator.instance); 567 568 predicate = 569 new ListColumnPredicate( 570 columns[j], 571 Arrays.asList((StarColumnPredicate[]) values)); 572 } 573 574 predicates[j] = predicate; 575 } 576 return predicates; 577 } 578 579 private void generateAggregateSql() { 580 final RolapCube cube = FastBatchingCellReader.this.cube; 581 if (cube == null || cube.isVirtual()) { 582 final StringBuilder buf = new StringBuilder(64); 583 buf.append("AggGen: Sorry, can not create SQL for virtual Cube \""); 584 buf.append(cube.getName()); 585 buf.append("\", operation not currently supported"); 586 BATCH_LOGGER.error(buf.toString()); 587 588 } else { 589 final AggGen aggGen = 590 new AggGen(cube.getName(), cube.getStar(), columns); 591 if (aggGen.isReady()) { 592 // PRINT TO STDOUT - DO NOT USE BATCH_LOGGER 593 System.out.println("createLost:" + 594 Util.nl + aggGen.createLost()); 595 System.out.println("insertIntoLost:" + 596 Util.nl + aggGen.insertIntoLost()); 597 System.out.println("createCollapsed:" + 598 Util.nl + aggGen.createCollapsed()); 599 System.out.println("insertIntoCollapsed:" + 600 Util.nl + aggGen.insertIntoCollapsed()); 601 } else { 602 BATCH_LOGGER.error("AggGen failed"); 603 } 604 } 605 } 606 607 /** 608 * Returns the first measure based upon a distinct aggregation, or null 609 * if there is none. 610 */ 611 final RolapStar.Measure getFirstDistinctMeasure( 612 List<RolapStar.Measure> measuresList) 613 { 614 for (RolapStar.Measure measure : measuresList) { 615 if (measure.getAggregator().isDistinct()) { 616 return measure; 617 } 618 } 619 return null; 620 } 621 622 /** 623 * Returns the number of the measures based upon a distinct 624 * aggregation. 625 */ 626 private int getDistinctMeasureCount( 627 List<RolapStar.Measure> measuresList) 628 { 629 int count = 0; 630 for (RolapStar.Measure measure : measuresList) { 631 if (measure.getAggregator().isDistinct()) { 632 ++count; 633 } 634 } 635 return count; 636 } 637 638 /** 639 * Returns the list of measures based upon a distinct aggregation 640 * containing SQL measure expressions(as opposed to column expressions). 641 * 642 * This method was initially intended for only those measures that are 643 * defined using subqueries(for DBs that support them). However, since 644 * Mondrian does not parse the SQL string, the method will count both 645 * queries as well as some non query SQL expressions. 646 */ 647 private List<RolapStar.Measure> getDistinctSqlMeasures( 648 List<RolapStar.Measure> measuresList) 649 { 650 List<RolapStar.Measure> distinctSqlMeasureList = 651 new ArrayList<RolapStar.Measure>(); 652 for (RolapStar.Measure measure : measuresList) { 653 if (measure.getAggregator().isDistinct() && 654 measure.getExpression() instanceof 655 MondrianDef.MeasureExpression) { 656 MondrianDef.MeasureExpression measureExpr = 657 (MondrianDef.MeasureExpression) measure.getExpression(); 658 MondrianDef.SQL measureSql = measureExpr.expressions[0]; 659 // Checks if the SQL contains "SELECT" to detect the case a 660 // subquery is used to define the measure. This is not a 661 // perfect check, because a SQL expression on column names 662 // containing "SELECT" will also be detected. e,g, 663 // count("select beef" + "regular beef"). 664 if (measureSql.cdata.toUpperCase().contains("SELECT")) { 665 distinctSqlMeasureList.add(measure); 666 } 667 } 668 } 669 return distinctSqlMeasureList; 670 } 671 672 /** 673 * Returns whether another Batch can be batched to this Batch. 674 * 675 * <p>This is possible if: 676 * <li>columns list is super set of other batch's constraint columns; 677 * and 678 * <li>both have same Fact Table; and 679 * <li>matching columns of this and other batch has the same value; and 680 * <li>non matching columns of this batch have ALL VALUES 681 * </ul> 682 */ 683 boolean canBatch(Batch other) { 684 return hasOverlappingBitKeys(other) 685 && constraintsMatch(other) 686 && hasSameMeasureList(other) 687 && haveSameStarAndAggregation(other); 688 } 689 690 /** 691 * Returns whether the constraints on this Batch subsume the constraints 692 * on another Batch and therefore the other Batch can be subsumed into 693 * this one for GROUPING SETS purposes. Not symmetric. 694 * 695 * @param other Other batch 696 * @return Whether other batch can be subsumed into this one 697 */ 698 private boolean constraintsMatch(Batch other) { 699 if (areBothDistinctCountBatches(other)) { 700 if (getConstrainedColumnsBitKey().equals( 701 other.getConstrainedColumnsBitKey())) { 702 return hasSameCompoundPredicate(other) 703 && haveSameValuesForOverlappingColumnsOrHasAllChildrenForOthers(other); 704 } else { 705 return hasSameCompoundPredicate(other) 706 || (other.batchKey.getCompoundPredicateList().isEmpty() 707 || equalConstraint( 708 batchKey.getCompoundPredicateList(), 709 other.batchKey.getCompoundPredicateList())) 710 && haveSameValuesForOverlappingColumnsOrHasAllChildrenForOthers(other); 711 } 712 } else { 713 return haveSameValuesForOverlappingColumnsOrHasAllChildrenForOthers(other); 714 } 715 } 716 717 private boolean equalConstraint( 718 List<StarPredicate> predList1, 719 List<StarPredicate> predList2) 720 { 721 if (predList1.size() != predList2.size()) { 722 return false; 723 } 724 for (int i = 0; i < predList1.size(); i++) { 725 StarPredicate pred1 = predList1.get(i); 726 StarPredicate pred2 = predList2.get(i); 727 if (!pred1.equalConstraint(pred2)) { 728 return false; 729 } 730 } 731 return true; 732 } 733 734 private boolean areBothDistinctCountBatches(Batch other) { 735 return this.hasDistinctCountMeasure() 736 && !this.hasNormalMeasures() 737 && other.hasDistinctCountMeasure() 738 && !other.hasNormalMeasures(); 739 } 740 741 private boolean hasNormalMeasures() { 742 return getDistinctMeasureCount(measuresList) != measuresList.size(); 743 } 744 745 private boolean hasSameMeasureList(Batch other) { 746 return (this.measuresList.size() == other.measuresList.size() && 747 this.measuresList.containsAll(other.measuresList)); 748 } 749 750 boolean hasOverlappingBitKeys(Batch other) { 751 return getConstrainedColumnsBitKey() 752 .isSuperSetOf(other.getConstrainedColumnsBitKey()); 753 } 754 755 boolean hasDistinctCountMeasure() { 756 return getDistinctMeasureCount(measuresList) > 0; 757 } 758 759 boolean hasSameCompoundPredicate(Batch other) { 760 final StarPredicate starPredicate = compoundPredicate(); 761 final StarPredicate otherStarPredicate = other.compoundPredicate(); 762 if (starPredicate == null && otherStarPredicate == null) { 763 return true; 764 } else if (starPredicate != null && otherStarPredicate != null) { 765 return starPredicate.equalConstraint(otherStarPredicate); 766 } 767 return false; 768 } 769 770 private StarPredicate compoundPredicate() { 771 StarPredicate predicate = null; 772 for (Set<StarColumnPredicate> valueSet : valueSets) { 773 StarPredicate orPredicate = null; 774 for (StarColumnPredicate starColumnPredicate : valueSet) { 775 if (orPredicate == null) { 776 orPredicate = starColumnPredicate; 777 } else { 778 orPredicate = orPredicate.or(starColumnPredicate); 779 } 780 } 781 if (predicate == null) { 782 predicate = orPredicate; 783 } else { 784 predicate = predicate.and(orPredicate); 785 } 786 } 787 for (StarPredicate starPredicate : this.batchKey.getCompoundPredicateList()) { 788 if (predicate == null) { 789 predicate = starPredicate; 790 } else { 791 predicate = predicate.and(starPredicate); 792 } 793 } 794 return predicate; 795 } 796 797 boolean haveSameStarAndAggregation(Batch other) { 798 boolean rollup[] = {false}; 799 boolean otherRollup[] = {false}; 800 801 boolean hasSameAggregation = getAgg(rollup) == other.getAgg(otherRollup); 802 boolean hasSameRollupOption = rollup[0] == otherRollup[0]; 803 804 boolean hasSameStar = getStar().equals(other.getStar()); 805 return hasSameStar && hasSameAggregation && hasSameRollupOption; 806 } 807 808 /** 809 * @param rollup Out parameter 810 * @return AggStar 811 */ 812 private AggStar getAgg(boolean[] rollup) { 813 814 AggregationManager aggregationManager = 815 AggregationManager.instance(); 816 AggStar star = 817 aggregationManager.findAgg(getStar(), 818 getConstrainedColumnsBitKey(), makeMeasureBitKey(), 819 rollup); 820 return star; 821 } 822 823 private BitKey makeMeasureBitKey() { 824 BitKey bitKey = getConstrainedColumnsBitKey().emptyCopy(); 825 for (RolapStar.Measure measure : measuresList) { 826 bitKey.set(measure.getBitPosition()); 827 } 828 return bitKey; 829 } 830 831 boolean haveSameValuesForOverlappingColumnsOrHasAllChildrenForOthers( 832 Batch other) 833 { 834 for (int j = 0; j < columns.length; j++) { 835 boolean isCommonColumn = false; 836 for (int i = 0; i < other.columns.length; i++) { 837 if (areSameColumns(other.columns[i], columns[j])) { 838 if (hasSameValues(other.valueSets[i], valueSets[j])) { 839 isCommonColumn = true; 840 break; 841 } else { 842 return false; 843 } 844 } 845 } 846 if (!isCommonColumn && 847 !hasAllValues(columns[j], valueSets[j])) { 848 return false; 849 } 850 } 851 return true; 852 } 853 854 private boolean hasAllValues( 855 RolapStar.Column column, 856 Set<StarColumnPredicate> valueSet) 857 { 858 return column.getCardinality() == valueSet.size(); 859 } 860 861 private boolean areSameColumns( 862 RolapStar.Column otherColumn, 863 RolapStar.Column thisColumn) 864 { 865 return otherColumn.equals(thisColumn); 866 } 867 868 private boolean hasSameValues( 869 Set<StarColumnPredicate> otherValueSet, 870 Set<StarColumnPredicate> thisValueSet) 871 { 872 return otherValueSet.equals(thisValueSet); 873 } 874 } 875 876 private static class CompositeBatchComparator 877 implements Comparator<CompositeBatch> 878 { 879 static final CompositeBatchComparator instance = 880 new CompositeBatchComparator(); 881 882 public int compare(CompositeBatch o1, CompositeBatch o2) { 883 return BatchComparator.instance.compare( 884 o1.detailedBatch, 885 o2.detailedBatch); 886 } 887 } 888 889 private static class BatchComparator implements Comparator<Batch> { 890 static final BatchComparator instance = new BatchComparator(); 891 892 private BatchComparator() { 893 } 894 895 public int compare( 896 Batch o1, Batch o2) { 897 if (o1.columns.length != o2.columns.length) { 898 return o1.columns.length - o2.columns.length; 899 } 900 for (int i = 0; i < o1.columns.length; i++) { 901 int c = o1.columns[i].getName().compareTo( 902 o2.columns[i].getName()); 903 if (c != 0) { 904 return c; 905 } 906 } 907 for (int i = 0; i < o1.columns.length; i++) { 908 int c = compare(o1.valueSets[i], o2.valueSets[i]); 909 if (c != 0) { 910 return c; 911 } 912 } 913 return 0; 914 } 915 916 <T> int compare(Set<T> set1, Set<T> set2) { 917 if (set1.size() != set2.size()) { 918 return set1.size() - set2.size(); 919 } 920 Iterator<T> iter1 = set1.iterator(); 921 Iterator<T> iter2 = set2.iterator(); 922 while (iter1.hasNext()) { 923 T v1 = iter1.next(); 924 T v2 = iter2.next(); 925 int c = Util.compareKey(v1, v2); 926 if (c != 0) { 927 return c; 928 } 929 } 930 return 0; 931 } 932 } 933 934 private static class ValueColumnConstraintComparator 935 implements Comparator<ValueColumnPredicate> 936 { 937 static final ValueColumnConstraintComparator instance = 938 new ValueColumnConstraintComparator(); 939 940 private ValueColumnConstraintComparator() { 941 } 942 943 public int compare( 944 ValueColumnPredicate o1, 945 ValueColumnPredicate o2) 946 { 947 Object v1 = o1.getValue(); 948 Object v2 = o2.getValue(); 949 if (v1.getClass() == v2.getClass() && 950 v1 instanceof Comparable) { 951 return ((Comparable) v1).compareTo(v2); 952 } else { 953 return v1.toString().compareTo(v2.toString()); 954 } 955 } 956 } 957 } 958 959 // End FastBatchingCellReader.java