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) 1998-2005 Julian Hyde 008// Copyright (C) 2005-2009 Pentaho and others 009// All Rights Reserved. 010*/ 011package mondrian.olap; 012 013import mondrian.calc.*; 014import mondrian.mdx.*; 015import mondrian.olap.type.DimensionType; 016import mondrian.olap.type.*; 017import mondrian.resource.MondrianResource; 018 019import java.io.PrintWriter; 020 021/** 022 * An axis in an MDX query. For example, the typical MDX query has two axes, 023 * which appear as the "ON COLUMNS" and "ON ROWS" clauses. 024 * 025 * @author jhyde, 20 January, 1999 026 */ 027public class QueryAxis extends QueryPart { 028 029 private boolean nonEmpty; 030 private boolean ordered; 031 private Exp exp; 032 private final AxisOrdinal axisOrdinal; 033 034 /** 035 * Whether to show subtotals on this axis. 036 * The "(show\hide)Subtotals" operation changes its valud. 037 */ 038 private SubtotalVisibility subtotalVisibility; 039 private final Id[] dimensionProperties; 040 041 /** 042 * Creates an axis. 043 * 044 * @param nonEmpty Whether to filter out members of this axis whose cells 045 * are all empty 046 * @param set Expression to populate the axis 047 * @param axisOrdinal Which axis (ROWS, COLUMNS, etc.) 048 * @param subtotalVisibility Whether to show subtotals 049 * @param dimensionProperties List of dimension properties 050 */ 051 public QueryAxis( 052 boolean nonEmpty, 053 Exp set, 054 AxisOrdinal axisOrdinal, 055 SubtotalVisibility subtotalVisibility, 056 Id[] dimensionProperties) 057 { 058 assert dimensionProperties != null; 059 assert axisOrdinal != null; 060 this.nonEmpty = nonEmpty 061 || (MondrianProperties.instance().EnableNonEmptyOnAllAxis.get() 062 && !axisOrdinal.isFilter()); 063 this.exp = set; 064 this.axisOrdinal = axisOrdinal; 065 this.subtotalVisibility = subtotalVisibility; 066 this.dimensionProperties = dimensionProperties; 067 this.ordered = false; 068 } 069 070 /** 071 * Creates an axis with no dimension properties. 072 * 073 * @see #QueryAxis(boolean,Exp,AxisOrdinal,mondrian.olap.QueryAxis.SubtotalVisibility,Id[]) 074 */ 075 public QueryAxis( 076 boolean nonEmpty, 077 Exp set, 078 AxisOrdinal axisOrdinal, 079 SubtotalVisibility subtotalVisibility) 080 { 081 this(nonEmpty, set, axisOrdinal, subtotalVisibility, new Id[0]); 082 } 083 084 public Object clone() { 085 return new QueryAxis( 086 nonEmpty, exp.clone(), axisOrdinal, 087 subtotalVisibility, dimensionProperties.clone()); 088 } 089 090 static QueryAxis[] cloneArray(QueryAxis[] a) { 091 QueryAxis[] a2 = new QueryAxis[a.length]; 092 for (int i = 0; i < a.length; i++) { 093 a2[i] = (QueryAxis) a[i].clone(); 094 } 095 return a2; 096 } 097 098 public Object accept(MdxVisitor visitor) { 099 final Object o = visitor.visit(this); 100 101 if (visitor.shouldVisitChildren()) { 102 // visit the expression which forms the axis 103 exp.accept(visitor); 104 } 105 return o; 106 } 107 108 public Calc compile(ExpCompiler compiler, ResultStyle resultStyle) { 109 Exp exp = this.exp; 110 if (axisOrdinal.isFilter()) { 111 exp = normalizeSlicerExpression(exp); 112 exp = exp.accept(compiler.getValidator()); 113 } 114 switch (resultStyle) { 115 case LIST: 116 return compiler.compileList(exp, false); 117 case MUTABLE_LIST: 118 return compiler.compileList(exp, true); 119 case ITERABLE: 120 return compiler.compileIter(exp); 121 default: 122 throw Util.unexpected(resultStyle); 123 } 124 } 125 126 private static Exp normalizeSlicerExpression(Exp exp) { 127 Exp slicer = exp; 128 if (slicer instanceof LevelExpr 129 || slicer instanceof HierarchyExpr 130 || slicer instanceof DimensionExpr) 131 { 132 slicer = new UnresolvedFunCall( 133 "DefaultMember", Syntax.Property, new Exp[] { 134 slicer}); 135 } 136 if (slicer == null) { 137 ; 138 } else if (slicer instanceof FunCall 139 && ((FunCall) slicer).getSyntax() == Syntax.Parentheses) 140 { 141 slicer = 142 new UnresolvedFunCall( 143 "{}", Syntax.Braces, new Exp[] {slicer}); 144 } else { 145 slicer = 146 new UnresolvedFunCall( 147 "{}", Syntax.Braces, new Exp[] { 148 new UnresolvedFunCall( 149 "()", Syntax.Parentheses, new Exp[] { 150 slicer})}); 151 } 152 153 return slicer; 154 } 155 156 public String getAxisName() { 157 return axisOrdinal.name(); 158 } 159 160 /** 161 * Returns the ordinal of this axis, for example 162 * {@link mondrian.olap.AxisOrdinal.StandardAxisOrdinal#ROWS}. 163 */ 164 public AxisOrdinal getAxisOrdinal() { 165 return axisOrdinal; 166 } 167 168 /** 169 * Returns whether the axis has the <code>NON EMPTY</code> property set. 170 */ 171 public boolean isNonEmpty() { 172 return nonEmpty; 173 } 174 175 /** 176 * Sets whether the axis has the <code>NON EMPTY</code> property set. 177 * See {@link #isNonEmpty()}. 178 */ 179 public void setNonEmpty(boolean nonEmpty) { 180 this.nonEmpty = nonEmpty; 181 } 182 183 /** 184 * Returns whether the axis has the <code>ORDER</code> property set. 185 */ 186 public boolean isOrdered() { 187 return ordered; 188 } 189 190 /** 191 * Sets whether the axis has the <code>ORDER</code> property set. 192 */ 193 public void setOrdered(boolean ordered) { 194 this.ordered = ordered; 195 } 196 197 /** 198 * Returns the expression which is used to compute the value of this axis. 199 */ 200 public Exp getSet() { 201 return exp; 202 } 203 204 /** 205 * Sets the expression which is used to compute the value of this axis. 206 * See {@link #getSet()}. 207 */ 208 public void setSet(Exp set) { 209 this.exp = set; 210 } 211 212 public void resolve(Validator validator) { 213 exp = validator.validate(exp, false); 214 final Type type = exp.getType(); 215 if (!TypeUtil.isSet(type)) { 216 // If expression is a member or a tuple, implicitly convert it 217 // into a set. Dimensions and hierarchies can be converted to 218 // members, thence to sets. 219 if (type instanceof MemberType 220 || type instanceof TupleType 221 || type instanceof DimensionType 222 || type instanceof HierarchyType) 223 { 224 exp = 225 new UnresolvedFunCall( 226 "{}", 227 Syntax.Braces, 228 new Exp[] {exp}); 229 exp = validator.validate(exp, false); 230 } else { 231 throw MondrianResource.instance().MdxAxisIsNotSet.ex( 232 axisOrdinal.name()); 233 } 234 } 235 } 236 237 public Object[] getChildren() { 238 return new Object[] {exp}; 239 } 240 241 public void unparse(PrintWriter pw) { 242 if (nonEmpty) { 243 pw.print("NON EMPTY "); 244 } 245 if (exp != null) { 246 exp.unparse(pw); 247 } 248 if (dimensionProperties.length > 0) { 249 pw.print(" DIMENSION PROPERTIES "); 250 for (int i = 0; i < dimensionProperties.length; i++) { 251 Id dimensionProperty = dimensionProperties[i]; 252 if (i > 0) { 253 pw.print(", "); 254 } 255 dimensionProperty.unparse(pw); 256 } 257 } 258 if (!axisOrdinal.isFilter()) { 259 pw.print(" ON " + axisOrdinal.name()); 260 } 261 } 262 263 public void addLevel(Level level) { 264 Util.assertTrue(level != null, "addLevel needs level"); 265 exp = new UnresolvedFunCall( 266 "Crossjoin", Syntax.Function, new Exp[] { 267 exp, 268 new UnresolvedFunCall( 269 "Members", Syntax.Property, new Exp[] { 270 new LevelExpr(level)})}); 271 } 272 273 void setSubtotalVisibility(boolean bShowSubtotals) { 274 subtotalVisibility = 275 bShowSubtotals 276 ? SubtotalVisibility.Show 277 : SubtotalVisibility.Hide; 278 } 279 280 public SubtotalVisibility getSubtotalVisibility() { 281 return subtotalVisibility; 282 } 283 284 public void resetSubtotalVisibility() { 285 this.subtotalVisibility = SubtotalVisibility.Undefined; 286 } 287 288 public void validate(Validator validator) { 289 if (axisOrdinal.isFilter()) { 290 if (exp != null) { 291 exp = validator.validate(exp, false); 292 } 293 } 294 } 295 296 public Id[] getDimensionProperties() { 297 return dimensionProperties; 298 } 299 300 /** 301 * <code>SubtotalVisibility</code> enumerates the allowed values of 302 * whether subtotals are visible. 303 */ 304 public enum SubtotalVisibility { 305 Undefined, 306 Hide, 307 Show; 308 } 309 310} 311 312// End QueryAxis.java