001    /*
002    // This software is subject to the terms of the Common Public License
003    // Agreement, available at the following URL:
004    // http://www.opensource.org/licenses/cpl.html.
005    // Copyright (C) 2004-2005 TONBELLER AG
006    // All Rights Reserved.
007    // You must accept the terms of that agreement to use this software.
008    */
009    package mondrian.rolap;
010    
011    import java.util.ArrayList;
012    import java.util.List;
013    
014    import mondrian.olap.*;
015    import mondrian.rolap.sql.SqlQuery;
016    import mondrian.mdx.MemberExpr;
017    
018    /**
019     * Creates SQL from parse tree nodes. Currently it creates the SQL that
020     * accesses a measure for the ORDER BY that is generated for a TopCount.<p/>
021     *
022     * @author av
023     * @since Nov 17, 2005
024     * @version $Id: //open/mondrian/src/main/mondrian/rolap/RolapNativeSql.java#18 $
025     */
026    public class RolapNativeSql {
027    
028        private SqlQuery sqlQuery;
029        private SqlQuery.Dialect dialect;
030    
031        CompositeSqlCompiler numericCompiler;
032        CompositeSqlCompiler booleanCompiler;
033    
034        RolapStoredMeasure storedMeasure;
035    
036        /**
037         * We remember one of the measures so we can generate
038         * the constraints from RolapAggregationManager. Also
039         * make sure all measures live in the same star.
040         *
041         * @see RolapAggregationManager#makeRequest(RolapEvaluator)
042         */
043        private boolean saveStoredMeasure(RolapStoredMeasure m) {
044            if (storedMeasure != null) {
045                RolapStar star1 = getStar(storedMeasure);
046                RolapStar star2 = getStar(m);
047                if (star1 != star2)
048                    return false;
049            }
050            this.storedMeasure = m;
051            return true;
052        }
053    
054        private RolapStar getStar(RolapStoredMeasure m) {
055            return ((RolapStar.Measure)m.getStarMeasure()).getStar();
056        }
057    
058        /**
059         * Translates an expression into SQL
060         *
061         * @author av
062         * @since Nov 23, 2005
063         */
064        interface SqlCompiler {
065            /**
066             * Returns SQL. If <code>exp</code> can not be compiled into SQL,
067             * returns null.
068             *
069             * @param exp Expression
070             * @return SQL, or null if cannot be converted into SQL
071             */
072            String compile(Exp exp);
073        }
074    
075        /**
076         * Implementation of {@link SqlCompiler} that uses chain of responsibility
077         * to find a matching sql compiler.
078         *
079         * @author av
080         * @since Nov 23, 2005
081         */
082        static class CompositeSqlCompiler implements SqlCompiler {
083            List<SqlCompiler> compilers = new ArrayList<SqlCompiler>();
084    
085            public void add(SqlCompiler compiler) {
086                compilers.add(compiler);
087            }
088    
089            public String compile(Exp exp) {
090                for (SqlCompiler compiler : compilers) {
091                    String s = compiler.compile(exp);
092                    if (s != null) {
093                        return s;
094                    }
095                }
096                return null;
097            }
098    
099            public String toString() {
100                return compilers.toString();
101            }
102    
103        }
104    
105        /**
106         * Compiles a numeric literal to SQL.
107         *
108         * @author av
109         * @since Nov 23, 2005
110         */
111        class NumberSqlCompiler implements SqlCompiler {
112            public String compile(Exp exp) {
113                if (!(exp instanceof Literal)) {
114                    return null;
115                }
116                if ((exp.getCategory() & Category.Numeric) == 0) {
117                    return null;
118                }
119                Literal literal = (Literal) exp;
120                String expr = String.valueOf(literal.getValue());
121                if (dialect.isDB2()) {
122                    expr = "FLOAT(" + expr + ")";
123                }
124                return expr;
125            }
126    
127            public String toString() {
128                return "NumberSqlCompiler";
129            }
130        }
131    
132        /**
133         * base class to remove MemberScalarExp
134         * @author av
135         * @since Nov 23, 2005
136         */
137        abstract class MemberSqlCompiler implements SqlCompiler {
138            protected Exp unwind(Exp exp) {
139                return exp;
140            }
141        }
142    
143        /**
144         * compiles a measure into SQL, the measure will be aggregated like <code>sum(measure)</code>
145         *
146         * @author av
147         * @since Nov 23, 2005
148         */
149        class StoredMeasureSqlCompiler extends MemberSqlCompiler {
150    
151            public String compile(Exp exp) {
152                exp = unwind(exp);
153                if (!(exp instanceof MemberExpr)) {
154                    return null;
155                }
156                final Member member = ((MemberExpr) exp).getMember();
157                if (!(member instanceof RolapStoredMeasure)) {
158                    return null;
159                }
160                RolapStoredMeasure measure = (RolapStoredMeasure) member;
161                if (measure.isCalculated()) {
162                    return null; // ??
163                }
164                if (!saveStoredMeasure(measure)) {
165                    return null;
166                }
167                String exprInner = measure.getMondrianDefExpression().getExpression(sqlQuery);
168                String expr = measure.getAggregator().getExpression(exprInner);
169                if (dialect.isDB2()) {
170                    expr = "FLOAT(" + expr + ")";
171                }
172                return expr;
173            }
174    
175            public String toString() {
176                return "StoredMeasureSqlCompiler";
177            }
178        }
179    
180        /**
181         * compiles the underlying expression of a calculated member
182         *
183         * @author av
184         * @since Nov 23, 2005
185         */
186        class CalculatedMemberSqlCompiler extends MemberSqlCompiler {
187            SqlCompiler compiler;
188    
189            CalculatedMemberSqlCompiler(SqlCompiler argumentCompiler) {
190                this.compiler = argumentCompiler;
191            }
192    
193            public String compile(Exp exp) {
194                exp = unwind(exp);
195                if (!(exp instanceof MemberExpr)) {
196                    return null;
197                }
198                final Member member = ((MemberExpr) exp).getMember();
199                if (!(member instanceof RolapCalculatedMember)) {
200                    return null;
201                }
202                exp = member.getExpression();
203                if (exp == null) {
204                    return null;
205                }
206                return compiler.compile(exp);
207            }
208    
209            public String toString() {
210                return "CalculatedMemberSqlCompiler";
211            }
212        }
213    
214        /**
215         * contains utility methods to compile FunCall expressions into SQL.
216         *
217         * @author av
218         * @since Nov 23, 2005
219         */
220        abstract class FunCallSqlCompilerBase implements SqlCompiler {
221            int category;
222            String mdx;
223            int argCount;
224    
225            FunCallSqlCompilerBase(int category, String mdx, int argCount) {
226                this.category = category;
227                this.mdx = mdx;
228                this.argCount = argCount;
229            }
230    
231            /**
232             * @return true if exp is a matching FunCall
233             */
234            protected boolean match(Exp exp) {
235                if ((exp.getCategory() & category) == 0) {
236                    return false;
237                }
238                if (!(exp instanceof FunCall)) {
239                    return false;
240                }
241                FunCall fc = (FunCall) exp;
242                if (!mdx.equalsIgnoreCase(fc.getFunName())) {
243                    return false;
244                }
245                Exp[] args = fc.getArgs();
246                if (args.length != argCount) {
247                    return false;
248                }
249                return true;
250            }
251    
252            /**
253             * compiles the arguments of a FunCall
254             *
255             * @return array of expressions or null if either exp does not match or
256             * any argument could not be compiled.
257             */
258            protected String[] compileArgs(Exp exp, SqlCompiler compiler) {
259                if (!match(exp)) {
260                    return null;
261                }
262                Exp[] args = ((FunCall) exp).getArgs();
263                String[] sqls = new String[args.length];
264                for (int i = 0; i < args.length; i++) {
265                    sqls[i] = compiler.compile(args[i]);
266                    if (sqls[i] == null) {
267                        return null;
268                    }
269                }
270                return sqls;
271            }
272        }
273    
274        /**
275         * compiles a funcall, e.g. foo(a, b, c)
276         * @author av
277         * @since Nov 23, 2005
278         */
279        class FunCallSqlCompiler extends FunCallSqlCompilerBase {
280            SqlCompiler compiler;
281            String sql;
282    
283            protected FunCallSqlCompiler(int category, String mdx, String sql,
284                    int argCount, SqlCompiler argumentCompiler) {
285                super(category, mdx, argCount);
286                this.sql = sql;
287                this.compiler = argumentCompiler;
288            }
289    
290            public String compile(Exp exp) {
291                String[] args = compileArgs(exp, compiler);
292                if (args == null) {
293                    return null;
294                }
295                StringBuilder buf = new StringBuilder();
296                buf.append(sql);
297                buf.append("(");
298                for (int i = 0; i < args.length; i++) {
299                    if (i > 0) {
300                        buf.append(", ");
301                    }
302                    buf.append(args[i]);
303                }
304                buf.append(") ");
305                return buf.toString();
306            }
307    
308            public String toString() {
309                return "FunCallSqlCompiler[" + mdx + "]";
310            }
311        }
312    
313        /**
314         * shortcut for an unary operator like NOT(a)
315         * @author av
316         * @since Nov 23, 2005
317         */
318        class UnaryOpSqlCompiler extends FunCallSqlCompiler {
319            protected UnaryOpSqlCompiler(int category, String mdx, String sql, SqlCompiler argumentCompiler) {
320                super(category, mdx, sql, 1, argumentCompiler);
321            }
322        }
323    
324        /**
325         * shortcut for ()
326         * @author av
327         * @since Nov 23, 2005
328         */
329        class ParenthesisSqlCompiler extends FunCallSqlCompiler {
330            protected ParenthesisSqlCompiler(int category, SqlCompiler argumentCompiler) {
331                super(category, "()", "", 1, argumentCompiler);
332            }
333    
334            public String toString() {
335                return "ParenthesisSqlCompiler";
336            }
337        }
338    
339        /**
340         * compiles an infix operator like addition into SQL like <code>(a + b)</code>
341         *
342         * @author av
343         * @since Nov 23, 2005
344         */
345        class InfixOpSqlCompiler extends FunCallSqlCompilerBase {
346            private final String sql;
347            private final SqlCompiler compiler;
348    
349            protected InfixOpSqlCompiler(int category, String mdx, String sql,
350                    SqlCompiler argumentCompiler) {
351                super(category, mdx, 2);
352                this.sql = sql;
353                this.compiler = argumentCompiler;
354            }
355    
356            public String compile(Exp exp) {
357                String[] args = compileArgs(exp, compiler);
358                if (args == null) {
359                    return null;
360                }
361                return "(" + args[0] + " " + sql + " " + args[1] + ")";
362            }
363    
364            public String toString() {
365                return "InfixSqlCompiler[" + mdx + "]";
366            }
367        }
368    
369        /**
370         * compiles an <code>IsEmpty(measure)</code>
371         * expression into SQL <code>measure is null</code>
372         *
373         */
374        class IsEmptySqlCompiler extends FunCallSqlCompilerBase {
375            private final SqlCompiler compiler;
376    
377            protected IsEmptySqlCompiler(int category, String mdx,
378                    SqlCompiler argumentCompiler) {
379                super(category, mdx, 1);
380                this.compiler = argumentCompiler;
381            }
382    
383            public String compile(Exp exp) {
384                String[] args = compileArgs(exp, compiler);
385                if (args == null) {
386                    return null;
387                }
388                return "(" + args[0] + " is null" + ")";
389            }
390    
391            public String toString() {
392                return "IsEmptySqlCompiler[" + mdx + "]";
393            }
394        }
395    
396        /**
397         * compiles an <code>IIF(cond, val1, val2)</code>
398         * expression into SQL <code>CASE WHEN cond THEN val1 ELSE val2 END</code>
399         *
400         * @author av
401         * @since Nov 23, 2005
402         */
403        class IifSqlCompiler extends FunCallSqlCompilerBase {
404    
405            SqlCompiler valueCompiler;
406    
407            IifSqlCompiler(int category, SqlCompiler valueCompiler) {
408                super(category, "iif", 3);
409                this.valueCompiler = valueCompiler;
410            }
411    
412            public String compile(Exp exp) {
413                if (!match(exp)) {
414                    return null;
415                }
416                Exp[] args = ((FunCall) exp).getArgs();
417                String cond = booleanCompiler.compile(args[0]);
418                String val1 = valueCompiler.compile(args[1]);
419                String val2 = valueCompiler.compile(args[2]);
420                if (cond == null || val1 == null || val2 == null) {
421                    return null;
422                }
423                return sqlQuery.getDialect().caseWhenElse(cond, val1, val2);
424            }
425    
426        }
427    
428        /**
429         * creates a new instance
430         * @param sqlQuery the query which is needed for differen SQL dialects - its not modified.
431         */
432        RolapNativeSql(SqlQuery sqlQuery) {
433            this.sqlQuery = sqlQuery;
434            this.dialect = sqlQuery.getDialect();
435    
436            numericCompiler = new CompositeSqlCompiler();
437            booleanCompiler = new CompositeSqlCompiler();
438    
439            numericCompiler.add(new NumberSqlCompiler());
440            numericCompiler.add(new StoredMeasureSqlCompiler());
441            numericCompiler.add(new CalculatedMemberSqlCompiler(numericCompiler));
442            numericCompiler.add(new ParenthesisSqlCompiler(Category.Numeric, numericCompiler));
443            numericCompiler.add(new InfixOpSqlCompiler(Category.Numeric, "+", "+", numericCompiler));
444            numericCompiler.add(new InfixOpSqlCompiler(Category.Numeric, "-", "-", numericCompiler));
445            numericCompiler.add(new InfixOpSqlCompiler(Category.Numeric, "/", "/", numericCompiler));
446            numericCompiler.add(new InfixOpSqlCompiler(Category.Numeric, "*", "*", numericCompiler));
447            numericCompiler.add(new IifSqlCompiler(Category.Numeric, numericCompiler));
448    
449            booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "<", "<", numericCompiler));
450            booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "<=", "<=", numericCompiler));
451            booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, ">", ">", numericCompiler));
452            booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, ">=", ">=", numericCompiler));
453            booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "=", "=", numericCompiler));
454            booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "<>", "<>", numericCompiler));
455            booleanCompiler.add(new IsEmptySqlCompiler(Category.Logical, "IsEmpty", numericCompiler));
456    
457            booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "and", "AND", booleanCompiler));
458            booleanCompiler.add(new InfixOpSqlCompiler(Category.Logical, "or", "OR", booleanCompiler));
459            booleanCompiler.add(new UnaryOpSqlCompiler(Category.Logical, "not", "NOT", booleanCompiler));
460            booleanCompiler.add(new ParenthesisSqlCompiler(Category.Logical, booleanCompiler));
461            booleanCompiler.add(new IifSqlCompiler(Category.Logical, booleanCompiler));
462        }
463    
464        /**
465         * generates an aggregate of a measure, e.g. "sum(Store_Sales)" for TopCount. The
466         * returned expr will be added to the select list and to the order by clause.
467         */
468        public String generateTopCountOrderBy(Exp exp) {
469            return numericCompiler.compile(exp);
470        }
471    
472        public String generateFilterCondition(Exp exp) {
473            return booleanCompiler.compile(exp);
474        }
475    
476        public RolapStoredMeasure getStoredMeasure() {
477            return storedMeasure;
478        }
479    
480    }
481    
482    // End RolapNativeSql.java