001    /*
002    // $Id: //open/mondrian/src/main/mondrian/rolap/agg/AbstractQuerySpec.java#14 $
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) 2005-2008 Julian Hyde and others
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    
011    package mondrian.rolap.agg;
012    
013    import mondrian.rolap.RolapStar;
014    import mondrian.rolap.StarColumnPredicate;
015    import mondrian.rolap.StarPredicate;
016    import mondrian.rolap.sql.SqlQuery;
017    import mondrian.olap.Util;
018    
019    import java.util.*;
020    
021    /**
022     * Base class for {@link QuerySpec} implementations.
023     *
024     * @author jhyde
025     * @author Richard M. Emberson
026     * @version $Id: //open/mondrian/src/main/mondrian/rolap/agg/AbstractQuerySpec.java#14 $
027     */
028    public abstract class AbstractQuerySpec implements QuerySpec {
029        private final RolapStar star;
030        protected final boolean countOnly;
031    
032        /**
033         * Creates an AbstractQuerySpec.
034         *
035         * @param star Star which defines columns of interest and their
036         * relationships
037         *
038         * @param countOnly If true, generate no GROUP BY clause, so the query
039         * returns a single row containing a grand total
040         */
041        protected AbstractQuerySpec(final RolapStar star, boolean countOnly) {
042            this.star = star;
043            this.countOnly = countOnly;
044        }
045    
046        /**
047         * Creates a query object.
048         *
049         * @return a new query object
050         */
051        protected SqlQuery newSqlQuery() {
052            return getStar().getSqlQuery();
053        }
054    
055        public RolapStar getStar() {
056            return star;
057        }
058    
059        /**
060         * Adds a measure to a query.
061         *
062         * @param i Ordinal of measure
063         * @param sqlQuery Query object
064         */
065        protected void addMeasure(final int i, final SqlQuery sqlQuery) {
066            RolapStar.Measure measure = getMeasure(i);
067            Util.assertTrue(measure.getTable() == getStar().getFactTable());
068            measure.getTable().addToFrom(sqlQuery, false, true);
069    
070            String exprInner = measure.generateExprString(sqlQuery);
071            String exprOuter = measure.getAggregator().getExpression(exprInner);
072            sqlQuery.addSelect(exprOuter, getMeasureAlias(i));
073        }
074    
075        protected abstract boolean isAggregate();
076    
077        protected void nonDistinctGenerateSql(SqlQuery sqlQuery)
078        {
079            // add constraining dimensions
080            RolapStar.Column[] columns = getColumns();
081            int arity = columns.length;
082            if (countOnly) {
083                sqlQuery.addSelect("count(*)");
084            }
085            for (int i = 0; i < arity; i++) {
086                RolapStar.Column column = columns[i];
087                RolapStar.Table table = column.getTable();
088                if (table.isFunky()) {
089                    // this is a funky dimension -- ignore for now
090                    continue;
091                }
092                table.addToFrom(sqlQuery, false, true);
093    
094                String expr = column.generateExprString(sqlQuery);
095    
096                StarColumnPredicate predicate = getColumnPredicate(i);
097                final String where = RolapStar.Column.createInExpr(
098                    expr,
099                    predicate,
100                    column.getDatatype(),
101                    sqlQuery);
102                if (!where.equals("true")) {
103                    sqlQuery.addWhere(where);
104                }
105    
106                if (countOnly) {
107                    continue;
108                }
109    
110                // some DB2 (AS400) versions throw an error, if a column alias is
111                // there and *not* used in a subsequent order by/group by
112                final SqlQuery.Dialect dialect = sqlQuery.getDialect();
113                if (dialect.isAS400()) {
114                    sqlQuery.addSelect(expr, null);
115                } else {
116                    sqlQuery.addSelect(expr, getColumnAlias(i));
117                }
118    
119                if (isAggregate()) {
120                    sqlQuery.addGroupBy(expr);
121                }
122    
123                // Add ORDER BY clause to make the results deterministic.
124                // Derby has a bug with ORDER BY, so ignore it.
125                if (isOrdered()) {
126                    sqlQuery.addOrderBy(expr, true, false, false);
127                }
128            }
129    
130            // Add compound member predicates
131            extraPredicates(sqlQuery);
132    
133            // add measures
134            if (!countOnly) {
135                for (int i = 0, count = getMeasureCount(); i < count; i++) {
136                    addMeasure(i, sqlQuery);
137                }
138            }
139        }
140    
141        /**
142         * Whether to add an ORDER BY clause to make results deterministic.
143         * Necessary if query returns more than one row and results are for
144         * human consumption.
145         *
146         * @return whether to sort query
147         */
148        protected boolean isOrdered() {
149            return false;
150        }
151    
152        public String generateSqlQuery() {
153            SqlQuery sqlQuery = newSqlQuery();
154    
155            int k = getDistinctMeasureCount();
156            final SqlQuery.Dialect dialect = sqlQuery.getDialect();
157            if (!dialect.allowsCountDistinct() && k > 0 ||
158                !dialect.allowsMultipleCountDistinct() && k > 1) {
159                distinctGenerateSql(sqlQuery, countOnly);
160            } else {
161                nonDistinctGenerateSql(sqlQuery);
162            }
163            if (!countOnly) {
164                addGroupingFunction(sqlQuery);
165                addGroupingSets(sqlQuery);
166            }
167            return sqlQuery.toString();
168        }
169    
170        protected void addGroupingFunction(SqlQuery sqlQuery) {
171            throw new UnsupportedOperationException();
172        }
173    
174        protected void addGroupingSets(SqlQuery sqlQuery) {
175            throw new UnsupportedOperationException();
176        }
177    
178        /**
179         * Returns the number of measures whose aggregation function is
180         * distinct-count.
181         *
182         * @return Number of distinct-count measures
183         */
184        protected int getDistinctMeasureCount() {
185            int k = 0;
186            for (int i = 0, count = getMeasureCount(); i < count; i++) {
187                RolapStar.Measure measure = getMeasure(i);
188                if (measure.getAggregator().isDistinct()) {
189                    ++k;
190                }
191            }
192            return k;
193        }
194    
195        /**
196         * Generates a SQL query to retrieve the values in this segment using
197         * an algorithm which converts distinct-aggregates to non-distinct
198         * aggregates over subqueries.
199         *
200         * @param outerSqlQuery Query to modify
201         * @param countOnly If true, only generate a single row: no need to
202         *   generate a GROUP BY clause or put any constraining columns in the
203         *   SELECT clause
204         */
205        protected void distinctGenerateSql(
206            final SqlQuery outerSqlQuery,
207            boolean countOnly)
208        {
209            final SqlQuery.Dialect dialect = outerSqlQuery.getDialect();
210            // Generate something like
211            //
212            //  select d0, d1, count(m0)
213            //  from (
214            //    select distinct dim1.x as d0, dim2.y as d1, f.z as m0
215            //    from f, dim1, dim2
216            //    where dim1.k = f.k1
217            //    and dim2.k = f.k2) as dummyname
218            //  group by d0, d1
219            //
220            // or, if countOnly=true
221            //
222            //  select count(m0)
223            //  from (
224            //    select distinct f.z as m0
225            //    from f, dim1, dim2
226            //    where dim1.k = f.k1
227            //    and dim2.k = f.k2) as dummyname
228    
229            final SqlQuery innerSqlQuery = newSqlQuery();
230            innerSqlQuery.setDistinct(true);
231    
232            // add constraining dimensions
233            RolapStar.Column[] columns = getColumns();
234            int arity = columns.length;
235            for (int i = 0; i < arity; i++) {
236                RolapStar.Column column = columns[i];
237                RolapStar.Table table = column.getTable();
238                if (table.isFunky()) {
239                    // this is a funky dimension -- ignore for now
240                    continue;
241                }
242                table.addToFrom(innerSqlQuery, false, true);
243                String expr = column.generateExprString(innerSqlQuery);
244                StarColumnPredicate predicate = getColumnPredicate(i);
245                final String where = RolapStar.Column.createInExpr(
246                    expr,
247                    predicate,
248                    column.getDatatype(),
249                    innerSqlQuery);
250                if (!where.equals("true")) {
251                    innerSqlQuery.addWhere(where);
252                }
253                if (countOnly) {
254                    continue;
255                }
256                final String alias = "d" + i;
257                innerSqlQuery.addSelect(expr, alias);
258                final String quotedAlias = dialect.quoteIdentifier(alias);
259                outerSqlQuery.addSelect(quotedAlias);
260                outerSqlQuery.addGroupBy(quotedAlias);
261            }
262    
263            // add predicates not associated with columns
264            extraPredicates(innerSqlQuery);
265    
266            // add measures
267            for (int i = 0, count = getMeasureCount(); i < count; i++) {
268                RolapStar.Measure measure = getMeasure(i);
269    
270                Util.assertTrue(measure.getTable() == getStar().getFactTable());
271                measure.getTable().addToFrom(innerSqlQuery, false, true);
272    
273                String alias = getMeasureAlias(i);
274                String expr = measure.generateExprString(outerSqlQuery);
275                innerSqlQuery.addSelect(expr, alias);
276    
277                outerSqlQuery.addSelect(
278                    measure.getAggregator().getNonDistinctAggregator().getExpression(
279                        dialect.quoteIdentifier(alias)));
280            }
281            outerSqlQuery.addFrom(innerSqlQuery, "dummyname", true);
282        }
283    
284        /**
285         * Adds predicates not associated with columns.
286         *
287         * @param sqlQuery Query
288         */
289        protected void extraPredicates(SqlQuery sqlQuery) {
290            List<StarPredicate> predicateList = getPredicateList();
291            for (StarPredicate predicate : predicateList) {
292                for (RolapStar.Column column :
293                    predicate.getConstrainedColumnList()) {
294                    final RolapStar.Table table = column.getTable();
295                    table.addToFrom(sqlQuery, false, true);
296                }
297                StringBuilder buf = new StringBuilder();
298                predicate.toSql(sqlQuery, buf);
299                final String where = buf.toString();
300                if (!where.equals("true")) {
301                    sqlQuery.addWhere(where);
302                }
303            }
304        }
305    
306        /**
307         * Returns a list of predicates not associated with a particular column.
308         *
309         * @return list of non-column predicates
310         */
311        protected List<StarPredicate> getPredicateList() {
312            return Collections.emptyList();
313        }
314    }
315    
316    
317    // End AbstractQuerySpec.java