001/* 002// $Id: Query.java 482 2012-01-05 23:27:27Z jhyde $ 003// 004// Licensed to Julian Hyde under one or more contributor license 005// agreements. See the NOTICE file distributed with this work for 006// additional information regarding copyright ownership. 007// 008// Julian Hyde licenses this file to you under the Apache License, 009// Version 2.0 (the "License"); you may not use this file except in 010// compliance with the License. You may obtain a copy of the License at: 011// 012// http://www.apache.org/licenses/LICENSE-2.0 013// 014// Unless required by applicable law or agreed to in writing, software 015// distributed under the License is distributed on an "AS IS" BASIS, 016// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017// See the License for the specific language governing permissions and 018// limitations under the License. 019*/ 020package org.olap4j.query; 021 022import org.olap4j.*; 023import org.olap4j.mdx.SelectNode; 024import org.olap4j.metadata.*; 025 026import java.sql.SQLException; 027import java.util.*; 028import java.util.Map.Entry; 029 030/** 031 * Base query model object. 032 * 033 * @author jhyde, jdixon, Luc Boudreau 034 * @version $Id: Query.java 482 2012-01-05 23:27:27Z jhyde $ 035 * @since May 29, 2007 036 */ 037public class Query extends QueryNodeImpl { 038 039 protected final String name; 040 protected Map<Axis, QueryAxis> axes = new HashMap<Axis, QueryAxis>(); 041 protected QueryAxis across; 042 protected QueryAxis down; 043 protected QueryAxis filter; 044 protected QueryAxis unused; 045 protected final Cube cube; 046 protected Map<String, QueryDimension> dimensionMap = 047 new HashMap<String, QueryDimension>(); 048 /** 049 * Whether or not to select the default hierarchy and default 050 * member on a dimension if no explicit selections were performed. 051 */ 052 protected boolean selectDefaultMembers = true; 053 private final OlapConnection connection; 054 private final SelectionFactory selectionFactory = new SelectionFactory(); 055 056 /** 057 * Constructs a Query object. 058 * @param name Any arbitrary name to give to this query. 059 * @param cube A Cube object against which to build a query. 060 * @throws SQLException If an error occurs while accessing the 061 * cube's underlying connection. 062 */ 063 public Query(String name, Cube cube) throws SQLException { 064 super(); 065 this.name = name; 066 this.cube = cube; 067 final Catalog catalog = cube.getSchema().getCatalog(); 068 this.connection = 069 catalog.getMetaData().getConnection().unwrap(OlapConnection.class); 070 this.connection.setCatalog(catalog.getName()); 071 this.unused = new QueryAxis(this, null); 072 for (Dimension dimension : cube.getDimensions()) { 073 QueryDimension queryDimension = new QueryDimension( 074 this, dimension); 075 unused.getDimensions().add(queryDimension); 076 dimensionMap.put(queryDimension.getName(), queryDimension); 077 } 078 across = new QueryAxis(this, Axis.COLUMNS); 079 down = new QueryAxis(this, Axis.ROWS); 080 filter = new QueryAxis(this, Axis.FILTER); 081 axes.put(null, unused); 082 axes.put(Axis.COLUMNS, across); 083 axes.put(Axis.ROWS, down); 084 axes.put(Axis.FILTER, filter); 085 } 086 087 /** 088 * Returns the MDX parse tree behind this Query. The returned object is 089 * generated for each call to this function. Altering the returned 090 * SelectNode object won't affect the query itself. 091 * @return A SelectNode object representing the current query structure. 092 */ 093 public SelectNode getSelect() { 094 return Olap4jNodeConverter.toOlap4j(this); 095 } 096 097 /** 098 * Returns the underlying cube object that is used to query against. 099 * @return The Olap4j's Cube object. 100 */ 101 public Cube getCube() { 102 return cube; 103 } 104 105 /** 106 * Returns the Olap4j's Dimension object according to the name 107 * given as a parameter. If no dimension of the given name is found, 108 * a null value will be returned. 109 * @param name The name of the dimension you want the object for. 110 * @return The dimension object, null if no dimension of that 111 * name can be found. 112 */ 113 public QueryDimension getDimension(String name) { 114 return dimensionMap.get(name); 115 } 116 117 /** 118 * Swaps rows and columns axes. Only applicable if there are two axes. 119 */ 120 public void swapAxes() { 121 // Only applicable if there are two axes - plus filter and unused. 122 if (axes.size() != 4) { 123 throw new IllegalArgumentException(); 124 } 125 List<QueryDimension> tmpAcross = new ArrayList<QueryDimension>(); 126 tmpAcross.addAll(across.getDimensions()); 127 128 List<QueryDimension> tmpDown = new ArrayList<QueryDimension>(); 129 tmpDown.addAll(down.getDimensions()); 130 131 across.getDimensions().clear(); 132 Map<Integer, QueryNode> acrossChildList = 133 new HashMap<Integer, QueryNode>(); 134 for (int cpt = 0; cpt < tmpAcross.size();cpt++) { 135 acrossChildList.put(Integer.valueOf(cpt), tmpAcross.get(cpt)); 136 } 137 across.notifyRemove(acrossChildList); 138 139 down.getDimensions().clear(); 140 Map<Integer, QueryNode> downChildList = 141 new HashMap<Integer, QueryNode>(); 142 for (int cpt = 0; cpt < tmpDown.size();cpt++) { 143 downChildList.put(Integer.valueOf(cpt), tmpDown.get(cpt)); 144 } 145 down.notifyRemove(downChildList); 146 147 across.getDimensions().addAll(tmpDown); 148 across.notifyAdd(downChildList); 149 150 down.getDimensions().addAll(tmpAcross); 151 down.notifyAdd(acrossChildList); 152 } 153 154 /** 155 * Returns the query axis for a given axis type. 156 * 157 * <p>If you pass axis=null, returns a special axis that is used to hold 158 * all unused hierarchies. (We may change this behavior in future.) 159 * 160 * @param axis Axis type 161 * @return Query axis 162 */ 163 public QueryAxis getAxis(Axis axis) { 164 return this.axes.get(axis); 165 } 166 167 /** 168 * Returns a map of the current query's axis. 169 * <p>Be aware that modifications to this list might 170 * have unpredictable consequences.</p> 171 * @return A standard Map object that represents the 172 * current query's axis. 173 */ 174 public Map<Axis, QueryAxis> getAxes() { 175 return axes; 176 } 177 178 /** 179 * Returns the fictional axis into which all unused dimensions are stored. 180 * All dimensions included in this axis will not be part of the query. 181 * @return The QueryAxis representing dimensions that are currently not 182 * used inside the query. 183 */ 184 public QueryAxis getUnusedAxis() { 185 return unused; 186 } 187 188 /** 189 * Safely disposes of all underlying objects of this 190 * query. 191 * @param closeConnection Whether or not to call the 192 * {@link OlapConnection#close()} method of the underlying 193 * connection. 194 */ 195 public void tearDown(boolean closeConnection) { 196 for (Entry<Axis, QueryAxis> entry : this.axes.entrySet()) { 197 entry.getValue().tearDown(); 198 } 199 this.axes.clear(); 200 this.clearListeners(); 201 if (closeConnection) { 202 try { 203 this.connection.close(); 204 } catch (SQLException e) { 205 e.printStackTrace(); 206 } 207 } 208 } 209 210 /** 211 * Safely disposes of all underlying objects of this 212 * query and closes the underlying {@link OlapConnection}. 213 * <p>Equivalent of calling Query.tearDown(true). 214 */ 215 public void tearDown() { 216 this.tearDown(true); 217 } 218 219 /** 220 * Validates the current query structure. If a dimension axis has 221 * been placed on an axis but no selections were performed on it, 222 * the default hierarchy and default member will be selected. This 223 * can be turned off by invoking the 224 * {@link Query#setSelectDefaultMembers(boolean)} method. 225 * @throws OlapException If the query is not valid, an exception 226 * will be thrown and it's message will describe exactly what to fix. 227 */ 228 public void validate() throws OlapException { 229 try { 230 // First, perform default selections if needed. 231 if (this.selectDefaultMembers) { 232 // Perform default selection on the dimensions on the rows axis. 233 for (QueryDimension dimension : this.getAxis(Axis.ROWS) 234 .getDimensions()) 235 { 236 if (dimension.getInclusions().size() == 0) { 237 Member defaultMember = dimension.getDimension() 238 .getDefaultHierarchy().getDefaultMember(); 239 dimension.include(defaultMember); 240 } 241 } 242 // Perform default selection on the 243 // dimensions on the columns axis. 244 for (QueryDimension dimension : this.getAxis(Axis.COLUMNS) 245 .getDimensions()) 246 { 247 if (dimension.getInclusions().size() == 0) { 248 Member defaultMember = dimension.getDimension() 249 .getDefaultHierarchy().getDefaultMember(); 250 dimension.include(defaultMember); 251 } 252 } 253 // Perform default selection on the dimensions 254 // on the filter axis. 255 for (QueryDimension dimension : this.getAxis(Axis.FILTER) 256 .getDimensions()) 257 { 258 if (dimension.getInclusions().size() == 0) { 259 Member defaultMember = dimension.getDimension() 260 .getDefaultHierarchy().getDefaultMember(); 261 dimension.include(defaultMember); 262 } 263 } 264 } 265 266 // We at least need a dimension on the rows and on the columns axis. 267 if (this.getAxis(Axis.ROWS).getDimensions().size() == 0) { 268 throw new OlapException( 269 "A valid Query requires at least one dimension on the rows axis."); 270 } 271 if (this.getAxis(Axis.COLUMNS).getDimensions().size() == 0) { 272 throw new OlapException( 273 "A valid Query requires at least one dimension on the columns axis."); 274 } 275 276 // Try to build a select tree. 277 this.getSelect(); 278 } catch (Exception e) { 279 throw new OlapException("Query validation failed.", e); 280 } 281 } 282 283 /** 284 * Executes the query against the current OlapConnection and returns 285 * a CellSet object representation of the data. 286 * 287 * @return A proper CellSet object that represents the query execution 288 * results. 289 * @throws OlapException If something goes sour, an OlapException will 290 * be thrown to the caller. It could be caused by many things, like 291 * a stale connection. Look at the root cause for more details. 292 */ 293 public CellSet execute() throws OlapException { 294 SelectNode mdx = getSelect(); 295 final Catalog catalog = cube.getSchema().getCatalog(); 296 try { 297 this.connection.setCatalog(catalog.getName()); 298 } catch (SQLException e) { 299 throw new OlapException("Error while executing query", e); 300 } 301 OlapStatement olapStatement = connection.createStatement(); 302 return olapStatement.executeOlapQuery(mdx); 303 } 304 305 /** 306 * Returns this query's name. There is no guarantee that it is unique 307 * and is set at object instanciation. 308 * @return This query's name. 309 */ 310 public String getName() { 311 return name; 312 } 313 314 /** 315 * Returns the current locale with which this query is expressed. 316 * @return A standard Locale object. 317 */ 318 public Locale getLocale() { 319 // REVIEW Do queries really support locales? 320 return Locale.getDefault(); 321 } 322 323 /** 324 * Package restricted method to access this query's selection factory. 325 * Usually used by query dimensions who wants to perform selections. 326 * @return The underlying SelectionFactory implementation. 327 */ 328 SelectionFactory getSelectionFactory() { 329 return selectionFactory; 330 } 331 332 /** 333 * Behavior setter for a query. By default, if a dimension is placed on 334 * an axis but no selections are made, the default hierarchy and 335 * the default member will be selected when validating the query. 336 * This behavior can be turned off by this setter. 337 * @param selectDefaultMembers Enables or disables the default 338 * member and hierarchy selection upon validation. 339 */ 340 public void setSelectDefaultMembers(boolean selectDefaultMembers) { 341 this.selectDefaultMembers = selectDefaultMembers; 342 } 343} 344 345// End Query.java