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) 2001-2005 Julian Hyde
008// Copyright (C) 2005-2013 Pentaho and others
009// All Rights Reserved.
010//
011// jhyde, 22 December, 2001
012*/
013package mondrian.rolap;
014
015import mondrian.calc.ExpCompiler;
016import mondrian.olap.*;
017import mondrian.olap.Member;
018import mondrian.olap.fun.FunUtil;
019import mondrian.resource.MondrianResource;
020import mondrian.rolap.RolapHierarchy.LimitedRollupMember;
021import mondrian.server.*;
022import mondrian.spi.Dialect;
023import mondrian.util.ClassResolver;
024
025import org.apache.log4j.Logger;
026
027import org.eigenbase.util.property.StringProperty;
028
029import java.io.*;
030import java.lang.reflect.*;
031import java.sql.SQLException;
032import java.util.*;
033
034import javax.sql.DataSource;
035
036/**
037 * Utility methods for classes in the <code>mondrian.rolap</code> package.
038 *
039 * @author jhyde
040 * @since 22 December, 2001
041 */
042public class RolapUtil {
043    public static final Logger MDX_LOGGER = Logger.getLogger("mondrian.mdx");
044    public static final Logger SQL_LOGGER = Logger.getLogger("mondrian.sql");
045    public static final Logger MONITOR_LOGGER =
046        Logger.getLogger("mondrian.server.monitor");
047    public static final Logger PROFILE_LOGGER =
048        Logger.getLogger("mondrian.profile");
049
050    static final Logger LOGGER = Logger.getLogger(RolapUtil.class);
051    private static Semaphore querySemaphore;
052
053    /**
054     * Special cell value indicates that the value is not in cache yet.
055     */
056    public static final Object valueNotReadyException = new Double(0);
057
058    /**
059     * Hook to run when a query is executed. This should not be
060     * used at runtime but only for testing.
061     */
062    private static ExecuteQueryHook queryHook = null;
063
064    /**
065     * Special value represents a null key.
066     */
067    public static final Comparable<?> sqlNullValue =
068        RolapUtilComparable.INSTANCE;
069
070    /**
071     * Wraps a schema reader in a proxy so that each call to schema reader
072     * has a locus for profiling purposes.
073     *
074     * @param connection Connection
075     * @param schemaReader Schema reader
076     * @return Wrapped schema reader
077     */
078    public static SchemaReader locusSchemaReader(
079        RolapConnection connection,
080        final SchemaReader schemaReader)
081    {
082        final Statement statement = connection.getInternalStatement();
083        final Execution execution = new Execution(statement, 0);
084        final Locus locus =
085            new Locus(
086                execution,
087                "Schema reader",
088                null);
089        return (SchemaReader) Proxy.newProxyInstance(
090            SchemaReader.class.getClassLoader(),
091            new Class[]{SchemaReader.class},
092            new InvocationHandler() {
093                public Object invoke(
094                    Object proxy,
095                    Method method,
096                    Object[] args)
097                    throws Throwable
098                {
099                    Locus.push(locus);
100                    try {
101                        return method.invoke(schemaReader, args);
102                    } catch (InvocationTargetException e) {
103                        throw e.getCause();
104                    } finally {
105                        Locus.pop(locus);
106                    }
107                }
108            }
109        );
110    }
111
112    /**
113     * Sets the query-execution hook used by tests. This method and
114     * {@link #setHook(mondrian.rolap.RolapUtil.ExecuteQueryHook)} are
115     * synchronized to ensure a memory barrier.
116     *
117     * @return Query execution hook
118     */
119    public static synchronized ExecuteQueryHook getHook() {
120        return queryHook;
121    }
122
123    public static synchronized void setHook(ExecuteQueryHook hook) {
124        queryHook = hook;
125    }
126
127    /**
128     * Comparable value, equal only to itself. Used to represent the NULL value,
129     * as returned from a SQL query.
130     */
131    private static final class RolapUtilComparable
132        implements Comparable, Serializable
133    {
134        private static final long serialVersionUID = -2595758291465179116L;
135
136        public static final RolapUtilComparable INSTANCE =
137            new RolapUtilComparable();
138
139        // singleton
140        private RolapUtilComparable() {
141        }
142
143        // do not override equals and hashCode -- use identity
144
145        public String toString() {
146            return "#null";
147        }
148
149        public int compareTo(Object o) {
150            // collates after everything (except itself)
151            return o == this ? 0 : -1;
152        }
153    }
154
155    /**
156     * A comparator singleton instance which can handle the presence of
157     * {@link RolapUtilComparable} instances in a collection.
158     */
159    public static final Comparator ROLAP_COMPARATOR =
160        new RolapUtilComparator();
161
162    private static final class RolapUtilComparator<T extends Comparable<T>>
163        implements Comparator<T>
164    {
165        public int compare(T o1, T o2) {
166            try {
167                return o1.compareTo(o2);
168            } catch (ClassCastException cce) {
169                if (o2 == RolapUtilComparable.INSTANCE) {
170                    return 1;
171                }
172                throw new MondrianException(cce);
173            }
174        }
175    }
176
177    /**
178     * Runtime NullMemberRepresentation property change not taken into
179     * consideration
180     */
181    private static String mdxNullLiteral = null;
182    public static final String sqlNullLiteral = "null";
183
184    public static String mdxNullLiteral() {
185        if (mdxNullLiteral == null) {
186            reloadNullLiteral();
187        }
188        return mdxNullLiteral;
189    }
190
191    public static void reloadNullLiteral() {
192        mdxNullLiteral =
193            MondrianProperties.instance().NullMemberRepresentation.get();
194    }
195
196    /**
197     * Names of classes of drivers we've loaded (or have tried to load).
198     *
199     * <p>NOTE: Synchronization policy: Lock the {@link RolapConnection} class
200     * before modifying or using this member.
201     */
202    private static final Set<String> loadedDrivers = new HashSet<String>();
203
204    static RolapMember[] toArray(List<RolapMember> v) {
205        return v.isEmpty()
206            ? new RolapMember[0]
207            : v.toArray(new RolapMember[v.size()]);
208    }
209
210    static RolapMember lookupMember(
211        MemberReader reader,
212        List<Id.Segment> uniqueNameParts,
213        boolean failIfNotFound)
214    {
215        RolapMember member =
216            lookupMemberInternal(
217                uniqueNameParts, null, reader, failIfNotFound);
218        if (member != null) {
219            return member;
220        }
221
222        // If this hierarchy has an 'all' member, we can omit it.
223        // For example, '[Gender].[(All Gender)].[F]' can be abbreviated
224        // '[Gender].[F]'.
225        final List<RolapMember> rootMembers = reader.getRootMembers();
226        if (rootMembers.size() == 1) {
227            final RolapMember rootMember = rootMembers.get(0);
228            if (rootMember.isAll()) {
229                member =
230                    lookupMemberInternal(
231                        uniqueNameParts, rootMember, reader, failIfNotFound);
232            }
233        }
234        return member;
235    }
236
237    private static RolapMember lookupMemberInternal(
238        List<Id.Segment> segments,
239        RolapMember member,
240        MemberReader reader,
241        boolean failIfNotFound)
242    {
243        for (Id.Segment segment : segments) {
244            if (!(segment instanceof Id.NameSegment)) {
245                break;
246            }
247            final Id.NameSegment nameSegment = (Id.NameSegment) segment;
248            List<RolapMember> children;
249            if (member == null) {
250                children = reader.getRootMembers();
251            } else {
252                children = new ArrayList<RolapMember>();
253                reader.getMemberChildren(member, children);
254                member = null;
255            }
256            for (RolapMember child : children) {
257                if (child.getName().equals(nameSegment.name)) {
258                    member = child;
259                    break;
260                }
261            }
262            if (member == null) {
263                break;
264            }
265        }
266        if (member == null && failIfNotFound) {
267            throw MondrianResource.instance().MdxCantFindMember.ex(
268                Util.implode(segments));
269        }
270        return member;
271    }
272
273    /**
274     * Executes a query, printing to the trace log if tracing is enabled.
275     *
276     * <p>If the query fails, it wraps the {@link SQLException} in a runtime
277     * exception with <code>message</code> as description, and closes the result
278     * set.
279     *
280     * <p>If it succeeds, the caller must call the {@link SqlStatement#close}
281     * method of the returned {@link SqlStatement}.
282     *
283     * @param dataSource DataSource
284     * @param sql SQL string
285     * @param locus Locus of execution
286     * @return ResultSet
287     */
288    public static SqlStatement executeQuery(
289        DataSource dataSource,
290        String sql,
291        Locus locus)
292    {
293        return executeQuery(dataSource, sql, null, 0, 0, locus, -1, -1, null);
294    }
295
296    /**
297     * Executes a query.
298     *
299     * <p>If the query fails, it wraps the {@link SQLException} in a runtime
300     * exception with <code>message</code> as description, and closes the result
301     * set.
302     *
303     * <p>If it succeeds, the caller must call the {@link SqlStatement#close}
304     * method of the returned {@link SqlStatement}.
305     *
306     *
307     * @param dataSource DataSource
308     * @param sql SQL string
309     * @param types Suggested types of columns, or null;
310     *     if present, must have one element for each SQL column;
311     *     each not-null entry overrides deduced JDBC type of the column
312     * @param maxRowCount Maximum number of rows to retrieve, <= 0 if unlimited
313     * @param firstRowOrdinal Ordinal of row to skip to (1-based), or 0 to
314     *   start from beginning
315     * @param locus Execution context of this statement
316     * @param resultSetType Result set type, or -1 to use default
317     * @param resultSetConcurrency Result set concurrency, or -1 to use default
318     * @return ResultSet
319     */
320    public static SqlStatement executeQuery(
321        DataSource dataSource,
322        String sql,
323        List<SqlStatement.Type> types,
324        int maxRowCount,
325        int firstRowOrdinal,
326        Locus locus,
327        int resultSetType,
328        int resultSetConcurrency,
329        Util.Functor1<Void, java.sql.Statement> callback)
330    {
331        SqlStatement stmt =
332            new SqlStatement(
333                dataSource, sql, types, maxRowCount, firstRowOrdinal, locus,
334                resultSetType, resultSetConcurrency, callback);
335        stmt.execute();
336        return stmt;
337    }
338
339    /**
340     * Raises an alert that native SQL evaluation could not be used
341     * in a case where it might have been beneficial, but some
342     * limitation in Mondrian's implementation prevented it.
343     * (Do not call this in cases where native evaluation would
344     * have been wasted effort.)
345     *
346     * @param functionName name of function for which native evaluation
347     * was skipped
348     *
349     * @param reason reason why native evaluation was skipped
350     */
351    public static void alertNonNative(
352        String functionName,
353        String reason)
354        throws NativeEvaluationUnsupportedException
355    {
356        // No i18n for log message, but yes for excn
357        String alertMsg =
358            "Unable to use native SQL evaluation for '" + functionName
359            + "'; reason:  " + reason;
360
361        StringProperty alertProperty =
362            MondrianProperties.instance().AlertNativeEvaluationUnsupported;
363        String alertValue = alertProperty.get();
364
365        if (alertValue.equalsIgnoreCase(
366                org.apache.log4j.Level.WARN.toString()))
367        {
368            LOGGER.warn(alertMsg);
369        } else if (alertValue.equalsIgnoreCase(
370                org.apache.log4j.Level.ERROR.toString()))
371        {
372            LOGGER.error(alertMsg);
373            throw MondrianResource.instance().NativeEvaluationUnsupported.ex(
374                functionName);
375        }
376    }
377
378    /**
379     * Loads a set of JDBC drivers.
380     *
381     * @param jdbcDrivers A string consisting of the comma-separated names
382     *  of JDBC driver classes. For example
383     *  <code>"sun.jdbc.odbc.JdbcOdbcDriver,com.mysql.jdbc.Driver"</code>.
384     */
385    public static synchronized void loadDrivers(String jdbcDrivers) {
386        StringTokenizer tok = new StringTokenizer(jdbcDrivers, ",");
387        while (tok.hasMoreTokens()) {
388            String jdbcDriver = tok.nextToken();
389            if (loadedDrivers.add(jdbcDriver)) {
390                try {
391                    ClassResolver.INSTANCE.forName(jdbcDriver, true);
392                    LOGGER.info(
393                        "Mondrian: JDBC driver "
394                        + jdbcDriver + " loaded successfully");
395                } catch (ClassNotFoundException e) {
396                    LOGGER.warn(
397                        "Mondrian: Warning: JDBC driver "
398                        + jdbcDriver + " not found");
399                }
400            }
401        }
402    }
403
404    /**
405     * Creates a compiler which will generate programs which will test
406     * whether the dependencies declared via
407     * {@link mondrian.calc.Calc#dependsOn(Hierarchy)} are accurate.
408     */
409    public static ExpCompiler createDependencyTestingCompiler(
410        ExpCompiler compiler)
411    {
412        return new RolapDependencyTestingEvaluator.DteCompiler(compiler);
413    }
414
415    /**
416     * Locates a member specified by its member name, from an array of
417     * members.  If an exact match isn't found, but a matchType of BEFORE
418     * or AFTER is specified, then the closest matching member is returned.
419     *
420     *
421     * @param members array of members to search from
422     * @param parent parent member corresponding to the member being searched
423     * for
424     * @param level level of the member
425     * @param searchName member name
426     * @param matchType match type
427     * @return matching member (if it exists) or the closest matching one
428     * in the case of a BEFORE or AFTER search
429     */
430    public static Member findBestMemberMatch(
431        List<? extends Member> members,
432        RolapMember parent,
433        RolapLevel level,
434        Id.Segment searchName,
435        MatchType matchType)
436    {
437        if (!(searchName instanceof Id.NameSegment)) {
438            return null;
439        }
440        final Id.NameSegment nameSegment = (Id.NameSegment) searchName;
441        switch (matchType) {
442        case FIRST:
443            return members.get(0);
444        case LAST:
445            return members.get(members.size() - 1);
446        default:
447            // fall through
448        }
449        // create a member corresponding to the member we're trying
450        // to locate so we can use it to hierarchically compare against
451        // the members array
452        Member searchMember =
453            level.getHierarchy().createMember(
454                parent, level, nameSegment.name, null);
455        Member bestMatch = null;
456        for (Member member : members) {
457            int rc;
458            if (searchName.quoting == Id.Quoting.KEY
459                && member instanceof RolapMember)
460            {
461                if (((RolapMember) member).getKey().toString().equals(
462                        nameSegment.name))
463                {
464                    return member;
465                }
466            }
467            if (matchType.isExact()) {
468                rc = Util.compareName(member.getName(), nameSegment.name);
469            } else {
470                rc =
471                    FunUtil.compareSiblingMembers(
472                        member,
473                        searchMember);
474            }
475            if (rc == 0) {
476                return member;
477            }
478            if (matchType == MatchType.BEFORE) {
479                if (rc < 0
480                    && (bestMatch == null
481                        || FunUtil.compareSiblingMembers(member, bestMatch)
482                        > 0))
483                {
484                    bestMatch = member;
485                }
486            } else if (matchType == MatchType.AFTER) {
487                if (rc > 0
488                    && (bestMatch == null
489                        || FunUtil.compareSiblingMembers(member, bestMatch)
490                        < 0))
491                {
492                    bestMatch = member;
493                }
494            }
495        }
496        if (matchType.isExact()) {
497            return null;
498        }
499        return bestMatch;
500    }
501
502    public static MondrianDef.Relation convertInlineTableToRelation(
503        MondrianDef.InlineTable inlineTable,
504        final Dialect dialect)
505    {
506        MondrianDef.View view = new MondrianDef.View();
507        view.alias = inlineTable.alias;
508
509        final int columnCount = inlineTable.columnDefs.array.length;
510        List<String> columnNames = new ArrayList<String>();
511        List<String> columnTypes = new ArrayList<String>();
512        for (int i = 0; i < columnCount; i++) {
513            columnNames.add(inlineTable.columnDefs.array[i].name);
514            columnTypes.add(inlineTable.columnDefs.array[i].type);
515        }
516        List<String[]> valueList = new ArrayList<String[]>();
517        for (MondrianDef.Row row : inlineTable.rows.array) {
518            String[] values = new String[columnCount];
519            for (MondrianDef.Value value : row.values) {
520                final int columnOrdinal = columnNames.indexOf(value.column);
521                if (columnOrdinal < 0) {
522                    throw Util.newError(
523                        "Unknown column '" + value.column + "'");
524                }
525                values[columnOrdinal] = value.cdata;
526            }
527            valueList.add(values);
528        }
529        view.addCode(
530            "generic",
531            dialect.generateInline(
532                columnNames,
533                columnTypes,
534                valueList));
535        return view;
536    }
537
538    public static RolapMember strip(RolapMember member) {
539        if (member instanceof RolapCubeMember) {
540            return ((RolapCubeMember) member).getRolapMember();
541        }
542        return member;
543    }
544
545    public static ExpCompiler createProfilingCompiler(ExpCompiler compiler) {
546        return new RolapProfilingEvaluator.ProfilingEvaluatorCompiler(
547            compiler);
548    }
549
550    /**
551     * Writes to a string and also to an underlying writer.
552     */
553    public static class TeeWriter extends FilterWriter {
554        StringWriter buf = new StringWriter();
555        public TeeWriter(Writer out) {
556            super(out);
557        }
558
559        /**
560         * Returns everything which has been written so far.
561         */
562        public String toString() {
563            return buf.toString();
564        }
565
566        /**
567         * Returns the underlying writer.
568         */
569        public Writer getWriter() {
570            return out;
571        }
572
573        public void write(int c) throws IOException {
574            super.write(c);
575            buf.write(c);
576        }
577
578        public void write(char cbuf[]) throws IOException {
579            super.write(cbuf);
580            buf.write(cbuf);
581        }
582
583        public void write(char cbuf[], int off, int len) throws IOException {
584            super.write(cbuf, off, len);
585            buf.write(cbuf, off, len);
586        }
587
588        public void write(String str) throws IOException {
589            super.write(str);
590            buf.write(str);
591        }
592
593        public void write(String str, int off, int len) throws IOException {
594            super.write(str, off, len);
595            buf.write(str, off, len);
596        }
597    }
598
599    /**
600     * Writer which throws away all input.
601     */
602    private static class NullWriter extends Writer {
603        public void write(char cbuf[], int off, int len) throws IOException {
604        }
605
606        public void flush() throws IOException {
607        }
608
609        public void close() throws IOException {
610        }
611    }
612
613    /**
614     * Gets the semaphore which controls how many people can run queries
615     * simultaneously.
616     */
617    static synchronized Semaphore getQuerySemaphore() {
618        if (querySemaphore == null) {
619            int queryCount = MondrianProperties.instance().QueryLimit.get();
620            querySemaphore = new Semaphore(queryCount);
621        }
622        return querySemaphore;
623    }
624
625    /**
626     * Creates a dummy evaluator.
627     */
628    public static Evaluator createEvaluator(
629        Statement statement)
630    {
631        Execution dummyExecution = new Execution(statement, 0);
632        final RolapResult result = new RolapResult(dummyExecution, false);
633        return result.getRootEvaluator();
634    }
635
636    /**
637     * A <code>Semaphore</code> is a primitive for process synchronization.
638     *
639     * <p>Given a semaphore initialized with <code>count</code>, no more than
640     * <code>count</code> threads can acquire the semaphore using the
641     * {@link #enter} method. Waiting threads block until enough threads have
642     * called {@link #leave}.
643     */
644    static class Semaphore {
645        private int count;
646        Semaphore(int count) {
647            if (count < 0) {
648                count = Integer.MAX_VALUE;
649            }
650            this.count = count;
651        }
652        synchronized void enter() {
653            if (count == Integer.MAX_VALUE) {
654                return;
655            }
656            if (count == 0) {
657                try {
658                    wait();
659                } catch (InterruptedException e) {
660                    throw Util.newInternal(e, "while waiting for semaphore");
661                }
662            }
663            Util.assertTrue(count > 0);
664            count--;
665        }
666        synchronized void leave() {
667            if (count == Integer.MAX_VALUE) {
668                return;
669            }
670            count++;
671            notify();
672        }
673    }
674
675    static interface ExecuteQueryHook {
676        void onExecuteQuery(String sql);
677    }
678
679    /**
680     * Modifies a bitkey so that it includes the proper bits
681     * for members in an array which should be considered
682     * as a limited rollup member.
683     */
684    public static void constraintBitkeyForLimitedMembers(
685        Evaluator evaluator,
686        Member[] members,
687        RolapCube cube,
688        BitKey levelBitKey)
689    {
690        // Limited Rollup Members have to be included in the bitkey
691        // so that we can pick the correct agg table.
692        for (Member curMember : members) {
693            if (curMember instanceof LimitedRollupMember) {
694                final int savepoint = evaluator.savepoint();
695                try {
696                    // set NonEmpty to false to avoid the possibility of
697                    // constraining member retrieval by context, which itself
698                    // requires determination of limited members, resulting
699                    // in infinite loop.
700                    evaluator.setNonEmpty(false);
701                    List<Member> lowestMembers =
702                        ((RolapHierarchy)curMember.getHierarchy())
703                            .getLowestMembersForAccess(
704                                evaluator,
705                                ((LimitedRollupMember)curMember)
706                                    .hierarchyAccess,
707                                FunUtil.getNonEmptyMemberChildrenWithDetails(
708                                    evaluator,
709                                    curMember));
710
711                    assert lowestMembers.size() > 0;
712
713                    Member lowMember = lowestMembers.get(0);
714
715                    while (true) {
716                        RolapStar.Column curColumn =
717                            ((RolapCubeLevel)lowMember.getLevel())
718                                .getBaseStarKeyColumn(cube);
719
720                        if (curColumn != null) {
721                            levelBitKey.set(curColumn.getBitPosition());
722                        }
723
724                        // If the level doesn't have unique members, we have to
725                        // add the parent levels until the keys are unique,
726                        // or all of them are added.
727                        if (!((RolapCubeLevel)lowMember
728                            .getLevel()).isUnique())
729                        {
730                            lowMember = lowMember.getParentMember();
731                            if (lowMember.isAll()) {
732                                break;
733                            }
734                        } else {
735                            break;
736                        }
737                    }
738                } finally {
739                    evaluator.restore(savepoint);
740                }
741            }
742        }
743    }
744}
745
746// End RolapUtil.java