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