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) 2003-2005 Julian Hyde 008// Copyright (C) 2005-2013 Pentaho 009// All Rights Reserved. 010*/ 011package mondrian.rolap; 012 013import mondrian.calc.Calc; 014import mondrian.calc.TupleList; 015import mondrian.olap.*; 016import mondrian.olap.fun.AggregateFunDef; 017import mondrian.olap.fun.FunUtil; 018import mondrian.spi.Dialect; 019import mondrian.spi.Dialect.Datatype; 020 021import java.util.List; 022 023/** 024 * Describes an aggregation operator, such as "sum" or "count". 025 * 026 * @author jhyde 027 * @since Jul 9, 2003 028 */ 029public abstract class RolapAggregator 030 extends EnumeratedValues.BasicValue 031 implements Aggregator 032{ 033 private static int index = 0; 034 035 public static final RolapAggregator Sum = 036 new RolapAggregator("sum", index++, false) { 037 public Object aggregate( 038 Evaluator evaluator, TupleList members, Calc exp) 039 { 040 return FunUtil.sum(evaluator, members, exp); 041 } 042 public boolean supportsFastAggregates(Dialect.Datatype dataType) { 043 switch (dataType) { 044 case Integer: 045 case Numeric: 046 return true; 047 default: 048 return false; 049 } 050 }; 051 public Object aggregate(List<Object> rawData, Datatype datatype) { 052 assert rawData.size() > 0; 053 switch (datatype) { 054 case Integer: 055 int sumInt = Integer.MIN_VALUE; 056 for (Object data : rawData) { 057 if (data != null) { 058 if (sumInt == Integer.MIN_VALUE) { 059 sumInt = 0; 060 } 061 if (data instanceof Double) { 062 data = ((Double) data).intValue(); 063 } 064 sumInt += (Integer) data; 065 } 066 } 067 return sumInt == Integer.MIN_VALUE 068 ? null 069 : sumInt; 070 case Numeric: 071 double sumDouble = Double.MIN_VALUE; 072 for (Object data : rawData) { 073 if (data != null) { 074 if (sumDouble == Double.MIN_VALUE) { 075 sumDouble = 0; 076 } 077 sumDouble += ((Number) data).doubleValue(); 078 } 079 } 080 return sumDouble == Double.MIN_VALUE 081 ? null 082 : sumDouble; 083 default: 084 throw new MondrianException( 085 "Aggregator " + this.name 086 + " does not support datatype" + datatype.name()); 087 } 088 } 089 }; 090 091 public static final RolapAggregator Count = 092 new RolapAggregator("count", index++, false) { 093 public Aggregator getRollup() { 094 return Sum; 095 } 096 097 public Object aggregate( 098 Evaluator evaluator, TupleList members, Calc exp) 099 { 100 return FunUtil.count(evaluator, members, false); 101 } 102 }; 103 104 public static final RolapAggregator Min = 105 new RolapAggregator("min", index++, false) { 106 public Object aggregate( 107 Evaluator evaluator, TupleList members, Calc exp) 108 { 109 return FunUtil.min(evaluator, members, exp); 110 } 111 public boolean supportsFastAggregates(Dialect.Datatype dataType) { 112 switch (dataType) { 113 case Integer: 114 case Numeric: 115 return true; 116 default: 117 return false; 118 } 119 }; 120 public Object aggregate(List<Object> rawData, Datatype datatype) { 121 assert rawData.size() > 0; 122 switch (datatype) { 123 case Integer: 124 int minInt = Integer.MAX_VALUE; 125 for (Object data : rawData) { 126 if (data != null) { 127 minInt = Math.min(minInt, (Integer)data); 128 } 129 } 130 return minInt == Integer.MAX_VALUE 131 ? null 132 : minInt; 133 case Numeric: 134 double minDouble = Double.MAX_VALUE; 135 for (Object data : rawData) { 136 if (data != null) { 137 minDouble = 138 Math.min( 139 minDouble, 140 ((Number)data).doubleValue()); 141 } 142 } 143 return minDouble == Double.MAX_VALUE 144 ? null 145 : minDouble; 146 default: 147 throw new MondrianException( 148 "Aggregator " + this.name 149 + " does not support datatype" + datatype.name()); 150 } 151 } 152 }; 153 154 public static final RolapAggregator Max = 155 new RolapAggregator("max", index++, false) { 156 public Object aggregate( 157 Evaluator evaluator, TupleList members, Calc exp) 158 { 159 return FunUtil.max(evaluator, members, exp); 160 } 161 public boolean supportsFastAggregates(Dialect.Datatype dataType) { 162 switch (dataType) { 163 case Integer: 164 case Numeric: 165 return true; 166 default: 167 return false; 168 } 169 }; 170 public Object aggregate(List<Object> rawData, Datatype datatype) { 171 assert rawData.size() > 0; 172 switch (datatype) { 173 case Integer: 174 int maxInt = Integer.MIN_VALUE; 175 for (Object data : rawData) { 176 if (data != null) { 177 maxInt = Math.max(maxInt, (Integer)data); 178 } 179 } 180 return maxInt == Integer.MIN_VALUE 181 ? null 182 : maxInt; 183 case Numeric: 184 double maxDouble = Double.MIN_VALUE; 185 for (Object data : rawData) { 186 if (data != null) { 187 maxDouble = 188 Math.max( 189 maxDouble, 190 ((Number)data).doubleValue()); 191 } 192 } 193 194 return maxDouble == Double.MIN_VALUE 195 ? null 196 : maxDouble; 197 default: 198 throw new MondrianException( 199 "Aggregator " + this.name 200 + " does not support datatype" + datatype.name()); 201 } 202 } 203 }; 204 205 public static final RolapAggregator Avg = 206 new RolapAggregator("avg", index++, false) { 207 public Aggregator getRollup() { 208 return new RolapAggregator("avg", index, false) { 209 public Object aggregate( 210 Evaluator evaluator, 211 TupleList members, 212 Calc calc) 213 { 214 return AggregateFunDef.avg(evaluator, members, calc); 215 } 216 }; 217 } 218 public Object aggregate( 219 Evaluator evaluator, TupleList members, Calc exp) 220 { 221 return AggregateFunDef.avg(evaluator, members, exp); 222 } 223 }; 224 225 public static final RolapAggregator DistinctCount = 226 new RolapAggregator("distinct-count", index++, true) { 227 public Aggregator getRollup() { 228 // Distinct counts cannot always be rolled up, when they can, 229 // it's using Sum. 230 return Sum; 231 } 232 233 public RolapAggregator getNonDistinctAggregator() { 234 return Count; 235 } 236 237 public Object aggregate( 238 Evaluator evaluator, TupleList members, Calc exp) 239 { 240 throw new UnsupportedOperationException(); 241 } 242 243 public String getExpression(String operand) { 244 return "count(distinct " + operand + ")"; 245 } 246 247 public boolean supportsFastAggregates( 248 mondrian.spi.Dialect.Datatype dataType) 249 { 250 // We can't rollup using the raw data, because this is 251 // a distinct-count operation. 252 return false; 253 }; 254 }; 255 256 /** 257 * List of all valid aggregation operators. 258 */ 259 public static final EnumeratedValues<RolapAggregator> enumeration = 260 new EnumeratedValues<RolapAggregator>( 261 new RolapAggregator[] {Sum, Count, Min, Max, Avg, DistinctCount}); 262 263 /** 264 * This is the base class for implementing aggregators over sum and 265 * average columns in an aggregate table. These differ from the above 266 * aggregators in that these require not oly the operand to create 267 * the aggregation String expression, but also, the aggregate table's 268 * fact count column expression. 269 * These aggregators are NOT singletons like the above aggregators; rather, 270 * each is different because of the fact count column expression. 271 */ 272 protected static abstract class BaseAggor extends RolapAggregator { 273 protected final String factCountExpr; 274 275 protected BaseAggor(final String name, final String factCountExpr) { 276 super(name, index++, false); 277 this.factCountExpr = factCountExpr; 278 } 279 280 public Object aggregate( 281 Evaluator evaluator, TupleList members, Calc exp) 282 { 283 throw new UnsupportedOperationException(); 284 } 285 } 286 287 /** 288 * Aggregator used for aggregate tables implementing the 289 * average aggregator. 290 * 291 * <p>It uses the aggregate table fact_count column 292 * and a sum measure to create the query used to generate an average: 293 * <blockquote> 294 * <code> 295 * avg == sum(column_sum) / sum(factcount). 296 * </code> 297 * </blockquote> 298 * 299 * <p>If the fact table has both a sum and average over the same column and 300 * the aggregate table only has a sum and fact count column, then the 301 * average aggregator can be generated using this aggregator. 302 */ 303 public static class AvgFromSum extends BaseAggor { 304 public AvgFromSum(String factCountExpr) { 305 super("AvgFromSum", factCountExpr); 306 } 307 public String getExpression(String operand) { 308 StringBuilder buf = new StringBuilder(64); 309 buf.append("sum("); 310 buf.append(operand); 311 buf.append(") / sum("); 312 buf.append(factCountExpr); 313 buf.append(')'); 314 return buf.toString(); 315 } 316 } 317 318 /** 319 * Aggregator used for aggregate tables implementing the 320 * average aggregator. 321 * 322 * <p>It uses the aggregate table fact_count column 323 * and an average measure to create the query used to generate an average: 324 * <blockquote> 325 * <code> 326 * avg == sum(column_sum * factcount) / sum(factcount). 327 * </code> 328 * </blockquote> 329 * 330 * <p>If the fact table has both a sum and average over the same column and 331 * the aggregate table only has a average and fact count column, then the 332 * average aggregator can be generated using this aggregator. 333 */ 334 public static class AvgFromAvg extends BaseAggor { 335 public AvgFromAvg(String factCountExpr) { 336 super("AvgFromAvg", factCountExpr); 337 } 338 public String getExpression(String operand) { 339 StringBuilder buf = new StringBuilder(64); 340 buf.append("sum("); 341 buf.append(operand); 342 buf.append(" * "); 343 buf.append(factCountExpr); 344 buf.append(") / sum("); 345 buf.append(factCountExpr); 346 buf.append(')'); 347 return buf.toString(); 348 } 349 } 350 /** 351 * This is an aggregator used for aggregate tables implementing the 352 * sum aggregator. It uses the aggregate table fact_count column 353 * and an average measure to create the query used to generate a sum: 354 * <pre> 355 * sum == sum(column_avg * factcount) 356 * </pre> 357 * If the fact table has both a sum and average over the same column and 358 * the aggregate table only has an average and fact count column, then the 359 * sum aggregator can be generated using this aggregator. 360 */ 361 public static class SumFromAvg extends BaseAggor { 362 public SumFromAvg(String factCountExpr) { 363 super("SumFromAvg", factCountExpr); 364 } 365 public String getExpression(String operand) { 366 StringBuilder buf = new StringBuilder(64); 367 buf.append("sum("); 368 buf.append(operand); 369 buf.append(" * "); 370 buf.append(factCountExpr); 371 buf.append(')'); 372 return buf.toString(); 373 } 374 } 375 376 377 378 379 private final boolean distinct; 380 381 public RolapAggregator(String name, int ordinal, boolean distinct) { 382 super(name, ordinal, null); 383 this.distinct = distinct; 384 } 385 386 public boolean isDistinct() { 387 return distinct; 388 } 389 390 /** 391 * Returns the expression to apply this aggregator to an operand. 392 * For example, <code>getExpression("emp.sal")</code> returns 393 * <code>"sum(emp.sal)"</code>. 394 */ 395 public String getExpression(String operand) { 396 StringBuilder buf = new StringBuilder(64); 397 buf.append(name); 398 buf.append('('); 399 if (distinct) { 400 buf.append("distinct "); 401 } 402 buf.append(operand); 403 buf.append(')'); 404 return buf.toString(); 405 } 406 407 /** 408 * If this is a distinct aggregator, returns the corresponding non-distinct 409 * aggregator, otherwise throws an error. 410 */ 411 public RolapAggregator getNonDistinctAggregator() { 412 throw new UnsupportedOperationException(); 413 } 414 415 /** 416 * Returns the aggregator used to roll up. By default, aggregators roll up 417 * themselves. 418 */ 419 public Aggregator getRollup() { 420 return this; 421 } 422 423 /** 424 * By default, fast rollup is not supported for all classes. 425 */ 426 public boolean supportsFastAggregates(Dialect.Datatype dataType) { 427 return false; 428 } 429 430 public Object aggregate( 431 List<Object> rawData, 432 Dialect.Datatype datatype) 433 { 434 throw new UnsupportedOperationException(); 435 } 436} 437 438// End RolapAggregator.java