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