001 /* 002 // $Id: //open/mondrian/src/main/mondrian/rolap/RolapUtil.java#57 $ 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) 2001-2002 Kana Software, Inc. 007 // Copyright (C) 2001-2008 Julian Hyde and others 008 // All Rights Reserved. 009 // You must accept the terms of that agreement to use this software. 010 // 011 // jhyde, 22 December, 2001 012 */ 013 014 package mondrian.rolap; 015 import mondrian.olap.*; 016 import mondrian.olap.fun.FunUtil; 017 import mondrian.resource.MondrianResource; 018 019 import org.apache.log4j.Logger; 020 import org.eigenbase.util.property.StringProperty; 021 import java.io.*; 022 import java.lang.reflect.Array; 023 import java.sql.SQLException; 024 import java.util.*; 025 026 import mondrian.calc.ExpCompiler; 027 import mondrian.rolap.sql.SqlQuery; 028 029 import javax.sql.DataSource; 030 031 /** 032 * Utility methods for classes in the <code>mondrian.rolap</code> package. 033 * 034 * @author jhyde 035 * @since 22 December, 2001 036 * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapUtil.java#57 $ 037 */ 038 public class RolapUtil { 039 public static final Logger MDX_LOGGER = Logger.getLogger("mondrian.mdx"); 040 public static final Logger SQL_LOGGER = Logger.getLogger("mondrian.sql"); 041 static final Logger LOGGER = Logger.getLogger(RolapUtil.class); 042 private static Semaphore querySemaphore; 043 044 /** 045 * Special cell value indicates that the value is not in cache yet. 046 */ 047 public static final Object valueNotReadyException = new Double(0); 048 049 /** 050 * Hook to run when a query is executed. 051 */ 052 static final ThreadLocal<ExecuteQueryHook> threadHooks = 053 new ThreadLocal<ExecuteQueryHook>(); 054 055 /** 056 * Special value represents a null key. 057 */ 058 public static final Comparable sqlNullValue = new Comparable() { 059 public boolean equals(Object o) { 060 return o == this; 061 } 062 public int hashCode() { 063 return super.hashCode(); 064 } 065 public String toString() { 066 return "#null"; 067 } 068 069 public int compareTo(Object o) { 070 return o == this ? 0 : -1; 071 } 072 }; 073 074 /** 075 * Runtime NullMemberRepresentation property change not taken into 076 * consideration 077 */ 078 public static final String mdxNullLiteral = 079 MondrianProperties.instance().NullMemberRepresentation.get(); 080 public static final String sqlNullLiteral = "null"; 081 082 /** 083 * Names of classes of drivers we've loaded (or have tried to load). 084 * 085 * <p>NOTE: Synchronization policy: Lock the {@link RolapConnection} class 086 * before modifying or using this member. 087 */ 088 private static final Set<String> loadedDrivers = new HashSet<String>(); 089 090 static RolapMember[] toArray(List<RolapMember> v) { 091 return v.isEmpty() 092 ? new RolapMember[0] 093 : v.toArray(new RolapMember[v.size()]); 094 } 095 096 static RolapMember lookupMember( 097 MemberReader reader, 098 List<Id.Segment> uniqueNameParts, 099 boolean failIfNotFound) 100 { 101 RolapMember member = 102 lookupMemberInternal( 103 uniqueNameParts, null, reader, failIfNotFound); 104 if (member != null) { 105 return member; 106 } 107 108 // If this hierarchy has an 'all' member, we can omit it. 109 // For example, '[Gender].[(All Gender)].[F]' can be abbreviated 110 // '[Gender].[F]'. 111 final List<RolapMember> rootMembers = reader.getRootMembers(); 112 if (rootMembers.size() == 1) { 113 final RolapMember rootMember = rootMembers.get(0); 114 if (rootMember.isAll()) { 115 member = 116 lookupMemberInternal( 117 uniqueNameParts, rootMember, reader, failIfNotFound); 118 } 119 } 120 return member; 121 } 122 123 private static RolapMember lookupMemberInternal( 124 List<Id.Segment> segments, 125 RolapMember member, 126 MemberReader reader, 127 boolean failIfNotFound) 128 { 129 for (Id.Segment segment : segments) { 130 List<RolapMember> children; 131 if (member == null) { 132 children = reader.getRootMembers(); 133 } else { 134 children = new ArrayList<RolapMember>(); 135 reader.getMemberChildren(member, children); 136 member = null; 137 } 138 for (RolapMember child : children) { 139 if (child.getName().equals(segment.name)) { 140 member = child; 141 break; 142 } 143 } 144 if (member == null) { 145 break; 146 } 147 } 148 if (member == null && failIfNotFound) { 149 throw MondrianResource.instance().MdxCantFindMember.ex( 150 Util.implode(segments)); 151 } 152 return member; 153 } 154 155 /** 156 * Adds an object to the end of an array. The resulting array is of the 157 * same type (e.g. <code>String[]</code>) as the input array. 158 */ 159 static <T> T[] addElement(T[] a, T o) { 160 Class clazz = a.getClass().getComponentType(); 161 T[] a2 = (T[]) Array.newInstance(clazz, a.length + 1); 162 System.arraycopy(a, 0, a2, 0, a.length); 163 a2[a.length] = o; 164 return a2; 165 } 166 167 /** 168 * Adds an array to the end of an array. The resulting array is of the 169 * same type (e.g. <code>String[]</code>) as the input array. 170 */ 171 static <T> T[] addElements(T[] a, T[] b) { 172 Class clazz = a.getClass().getComponentType(); 173 T[] c = (T[]) Array.newInstance(clazz, a.length + b.length); 174 System.arraycopy(a, 0, c, 0, a.length); 175 System.arraycopy(b, 0, c, a.length, b.length); 176 return c; 177 } 178 179 /** 180 * Executes a query, printing to the trace log if tracing is enabled. 181 * 182 * <p>If the query fails, it wraps the {@link SQLException} in a runtime 183 * exception with <code>message</code> as description, and closes the result 184 * set. 185 * 186 * <p>If it succeeds, the caller must call the {@link SqlStatement#close} 187 * method of the returned {@link SqlStatement}. 188 * 189 * @param dataSource DataSource 190 * @param sql SQL string 191 * @param component Description of a the component executing the query, 192 * generally a method name, e.g. "SqlTupleReader.readTuples" 193 * @param message Description of the purpose of this statement, to be 194 * printed if there is an error 195 * @return ResultSet 196 */ 197 public static SqlStatement executeQuery( 198 DataSource dataSource, 199 String sql, 200 String component, 201 String message) 202 { 203 return executeQuery( 204 dataSource, sql, -1, component, message, -1, -1); 205 } 206 207 /** 208 * Executes a query. 209 * 210 * <p>If the query fails, it wraps the {@link SQLException} in a runtime 211 * exception with <code>message</code> as description, and closes the result 212 * set. 213 * 214 * <p>If it succeeds, the caller must call the {@link SqlStatement#close} 215 * method of the returned {@link SqlStatement}. 216 * 217 * @param dataSource DataSource 218 * @param sql SQL string 219 * @param maxRows Row limit, or -1 if no limit 220 * @param component Description of a the component executing the query, 221 * generally a method name, e.g. "SqlTupleReader.readTuples" 222 * @param message Description of the purpose of this statement, to be 223 * printed if there is an error 224 * @param resultSetType Result set type, or -1 to use default 225 * @param resultSetConcurrency Result set concurrency, or -1 to use default 226 * @return ResultSet 227 */ 228 public static SqlStatement executeQuery( 229 DataSource dataSource, 230 String sql, 231 int maxRows, 232 String component, 233 String message, 234 int resultSetType, 235 int resultSetConcurrency) 236 { 237 SqlStatement stmt = 238 new SqlStatement( 239 dataSource, sql, maxRows, component, message, 240 resultSetType, resultSetConcurrency); 241 try { 242 stmt.execute(); 243 return stmt; 244 } catch (SQLException e) { 245 throw stmt.handle(e); 246 } 247 } 248 249 /** 250 * Raises an alert that native SQL evaluation could not be used 251 * in a case where it might have been beneficial, but some 252 * limitation in Mondrian's implementation prevented it. 253 * (Do not call this in cases where native evaluation would 254 * have been wasted effort.) 255 * 256 * @param functionName name of function for which native evaluation 257 * was skipped 258 * 259 * @param reason reason why native evaluation was skipped 260 */ 261 public static void alertNonNative( 262 String functionName, String reason) 263 throws NativeEvaluationUnsupportedException { 264 265 // No i18n for log message, but yes for excn 266 String alertMsg = 267 "Unable to use native SQL evaluation for '" + functionName 268 + "'; reason: " + reason; 269 270 StringProperty alertProperty = 271 MondrianProperties.instance().AlertNativeEvaluationUnsupported; 272 String alertValue = alertProperty.get(); 273 274 if (alertValue.equalsIgnoreCase( 275 org.apache.log4j.Level.WARN.toString())) { 276 LOGGER.warn(alertMsg); 277 } else if (alertValue.equalsIgnoreCase( 278 org.apache.log4j.Level.ERROR.toString())) { 279 LOGGER.error(alertMsg); 280 throw MondrianResource.instance().NativeEvaluationUnsupported.ex( 281 functionName); 282 } 283 } 284 285 /** 286 * Loads a set of JDBC drivers. 287 * 288 * @param jdbcDrivers A string consisting of the comma-separated names 289 * of JDBC driver classes. For example 290 * <code>"sun.jdbc.odbc.JdbcOdbcDriver,com.mysql.jdbc.Driver"</code>. 291 */ 292 public static synchronized void loadDrivers(String jdbcDrivers) { 293 StringTokenizer tok = new StringTokenizer(jdbcDrivers, ","); 294 while (tok.hasMoreTokens()) { 295 String jdbcDriver = tok.nextToken(); 296 if (loadedDrivers.add(jdbcDriver)) { 297 try { 298 Class.forName(jdbcDriver); 299 LOGGER.info("Mondrian: JDBC driver " 300 + jdbcDriver + " loaded successfully"); 301 } catch (ClassNotFoundException e) { 302 LOGGER.warn("Mondrian: Warning: JDBC driver " 303 + jdbcDriver + " not found"); 304 } 305 } 306 } 307 } 308 309 /** 310 * Creates a compiler which will generate programs which will test 311 * whether the dependencies declared via 312 * {@link mondrian.calc.Calc#dependsOn(mondrian.olap.Dimension)} are 313 * accurate. 314 */ 315 public static ExpCompiler createDependencyTestingCompiler( 316 ExpCompiler compiler) { 317 return new RolapDependencyTestingEvaluator.DteCompiler(compiler); 318 } 319 320 /** 321 * Locates a member specified by its member name, from an array of 322 * members. If an exact match isn't found, but a matchType of BEFORE 323 * or AFTER is specified, then the closest matching member is returned. 324 * 325 * @param members array of members to search from 326 * @param parent parent member corresponding to the member being searched 327 * for 328 * @param level level of the member 329 * @param searchName member name 330 * @param matchType match type 331 * @param caseInsensitive if true, use case insensitive search (if 332 * applicable) when when doing exact searches 333 * 334 * @return matching member (if it exists) or the closest matching one 335 * in the case of a BEFORE or AFTER search 336 */ 337 public static Member findBestMemberMatch( 338 List<? extends Member> members, 339 RolapMember parent, 340 RolapLevel level, 341 Id.Segment searchName, 342 MatchType matchType, 343 boolean caseInsensitive) 344 { 345 // create a member corresponding to the member we're trying 346 // to locate so we can use it to hierarchically compare against 347 // the members array 348 Member searchMember = level.getHierarchy().createMember(parent, level, searchName.name, null); 349 Member bestMatch = null; 350 for (Member member : members) { 351 int rc; 352 if (searchName.quoting == Id.Quoting.KEY 353 && member instanceof RolapMember) { 354 if (((RolapMember) member).getKey().toString() 355 .equals(searchName.name)) { 356 return member; 357 } 358 } 359 if (matchType == MatchType.EXACT) { 360 if (caseInsensitive) { 361 rc = Util.compareName(member.getName(), searchName.name); 362 } else { 363 rc = member.getName().compareTo(searchName.name); 364 } 365 } else { 366 rc = 367 FunUtil.compareSiblingMembers( 368 member, 369 searchMember); 370 } 371 if (rc == 0) { 372 return member; 373 } 374 if (matchType == MatchType.BEFORE) { 375 if (rc < 0 && 376 (bestMatch == null || 377 FunUtil.compareSiblingMembers(member, bestMatch) > 0)) { 378 bestMatch = member; 379 } 380 } else if (matchType == MatchType.AFTER) { 381 if (rc > 0 && 382 (bestMatch == null || 383 FunUtil.compareSiblingMembers(member, bestMatch) < 0)) { 384 bestMatch = member; 385 } 386 } 387 } 388 if (matchType == MatchType.EXACT) { 389 return null; 390 } 391 return bestMatch; 392 } 393 394 public static MondrianDef.Relation convertInlineTableToRelation( 395 MondrianDef.InlineTable inlineTable, 396 final SqlQuery.Dialect dialect) 397 { 398 MondrianDef.View view = new MondrianDef.View(); 399 view.alias = inlineTable.alias; 400 401 final int columnCount = inlineTable.columnDefs.array.length; 402 List<String> columnNames = new ArrayList<String>(); 403 List<String> columnTypes = new ArrayList<String>(); 404 for (int i = 0; i < columnCount; i++) { 405 columnNames.add(inlineTable.columnDefs.array[i].name); 406 columnTypes.add(inlineTable.columnDefs.array[i].type); 407 } 408 List<String[]> valueList = new ArrayList<String[]>(); 409 for (MondrianDef.Row row : inlineTable.rows.array) { 410 String[] values = new String[columnCount]; 411 for (MondrianDef.Value value : row.values) { 412 final int columnOrdinal = columnNames.indexOf(value.column); 413 if (columnOrdinal < 0) { 414 throw Util.newError( 415 "Unknown column '" + value.column + "'"); 416 } 417 values[columnOrdinal] = value.cdata; 418 } 419 valueList.add(values); 420 } 421 view.addCode( 422 "generic", 423 dialect.generateInline( 424 columnNames, 425 columnTypes, 426 valueList)); 427 return view; 428 } 429 430 /** 431 * Writes to a string and also to an underlying writer. 432 */ 433 public static class TeeWriter extends FilterWriter { 434 StringWriter buf = new StringWriter(); 435 public TeeWriter(Writer out) { 436 super(out); 437 } 438 439 /** 440 * Returns everything which has been written so far. 441 */ 442 public String toString() { 443 return buf.toString(); 444 } 445 446 /** 447 * Returns the underlying writer. 448 */ 449 public Writer getWriter() { 450 return out; 451 } 452 453 public void write(int c) throws IOException { 454 super.write(c); 455 buf.write(c); 456 } 457 458 public void write(char cbuf[]) throws IOException { 459 super.write(cbuf); 460 buf.write(cbuf); 461 } 462 463 public void write(char cbuf[], int off, int len) throws IOException { 464 super.write(cbuf, off, len); 465 buf.write(cbuf, off, len); 466 } 467 468 public void write(String str) throws IOException { 469 super.write(str); 470 buf.write(str); 471 } 472 473 public void write(String str, int off, int len) throws IOException { 474 super.write(str, off, len); 475 buf.write(str, off, len); 476 } 477 } 478 479 /** 480 * Writer which throws away all input. 481 */ 482 private static class NullWriter extends Writer { 483 public void write(char cbuf[], int off, int len) throws IOException { 484 } 485 486 public void flush() throws IOException { 487 } 488 489 public void close() throws IOException { 490 } 491 } 492 493 /** 494 * Gets the semaphore which controls how many people can run queries 495 * simultaneously. 496 */ 497 static synchronized Semaphore getQuerySemaphore() { 498 if (querySemaphore == null) { 499 int queryCount = MondrianProperties.instance().QueryLimit.get(); 500 querySemaphore = new Semaphore(queryCount); 501 } 502 return querySemaphore; 503 } 504 505 /** 506 * Creates a dummy evaluator. 507 */ 508 public static Evaluator createEvaluator(Query query) { 509 final RolapResult result = new RolapResult(query, false); 510 return result.getRootEvaluator(); 511 } 512 513 /** 514 * A <code>Semaphore</code> is a primitive for process synchronization. 515 * 516 * <p>Given a semaphore initialized with <code>count</code>, no more than 517 * <code>count</code> threads can acquire the semaphore using the 518 * {@link #enter} method. Waiting threads block until enough threads have 519 * called {@link #leave}. 520 */ 521 static class Semaphore { 522 private int count; 523 Semaphore(int count) { 524 if (count < 0) { 525 count = Integer.MAX_VALUE; 526 } 527 this.count = count; 528 } 529 synchronized void enter() { 530 if (count == Integer.MAX_VALUE) { 531 return; 532 } 533 if (count == 0) { 534 try { 535 wait(); 536 } catch (InterruptedException e) { 537 throw Util.newInternal(e, "while waiting for semaphore"); 538 } 539 } 540 Util.assertTrue(count > 0); 541 count--; 542 } 543 synchronized void leave() { 544 if (count == Integer.MAX_VALUE) { 545 return; 546 } 547 count++; 548 notify(); 549 } 550 } 551 552 static interface ExecuteQueryHook { 553 void onExecuteQuery(String sql); 554 } 555 556 } 557 558 // End RolapUtil.java