Package com.google.visualization.datasource

Source Code of com.google.visualization.datasource.QuerySplitter

// Copyright 2009 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.visualization.datasource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.List;

import com.google.common.collect.Lists;
import com.google.visualization.datasource.base.DataSourceException;
import com.google.visualization.datasource.base.InvalidQueryException;
import com.google.visualization.datasource.base.ReasonType;
import com.google.visualization.datasource.query.AbstractColumn;
import com.google.visualization.datasource.query.AggregationColumn;
import com.google.visualization.datasource.query.AggregationType;
import com.google.visualization.datasource.query.Query;
import com.google.visualization.datasource.query.QueryFormat;
import com.google.visualization.datasource.query.QueryGroup;
import com.google.visualization.datasource.query.QueryLabels;
import com.google.visualization.datasource.query.QuerySelection;
import com.google.visualization.datasource.query.SimpleColumn;

/**
* A utility class for splitting the user query into a data source query and a completion query.
* The data source query is executed  by the data source, the completion query is then
* executed by the query engine.
* The splitting is performed based on the capabilities that the data source declares it can
* handle.
*
* @author Yonatan B.Y.
*/
public final class QuerySplitter {
    private static final Log log = LogFactory.getLog(QuerySplitter.class);

    /**
     * Private constructor.
     */
    private QuerySplitter() {
    }

    /**
     * Split the query into a data source query and completion query. The data source query runs
     * first directly on the underlying data. The completion query is run by
     * {@link com.google.visualization.datasource.query.engine.QueryEngine} engine on the result of
     * the data source query.
     *
     * @param query        The <code>Query</code> to split.
     * @param capabilities The capabilities supported by the data source.
     * @return A split query.
     * @throws DataSourceException Thrown if the capabilities are not supported.
     */
    public static QueryPair splitQuery(Query query, Capabilities capabilities)
            throws DataSourceException {
        switch(capabilities) {
            case ALL:
                return splitAll(query);
            case NONE:
                return splitNone(query);
            case SQL:
                return splitSQL(query);
            case SORT_AND_PAGINATION:
                return splitSortAndPagination(query);
            case SELECT:
                return splitSelect(query);
        }
        log.error("Capabilities not supported.");
        throw new DataSourceException(ReasonType.NOT_SUPPORTED, "Capabilities not supported.");
    }

    /**
     * Splits the query for a data source with capabilities ALL. In this case, the original query is
     * copied to the data source query and the original query is empty.
     *
     * @param query The query to split.
     * @return The split query.
     */
    private static QueryPair splitAll(Query query) {
        Query dataSourceQuery = new Query();
        dataSourceQuery.copyFrom(query);
        Query completionQuery = new Query();
        return new QueryPair(dataSourceQuery, completionQuery);
    }

    /**
     * Splits the query for a data source with capabilities NONE. In this case, the original query is
     * copied to the completionQuery and the data source query is empty. Furthermore, the data source
     * query is assigned null and shouldn't be used or referenced by the data source.
     *
     * @param query The query to split.
     * @return The split query.
     */
    private static QueryPair splitNone(Query query) {
        Query completionQuery = new Query();
        completionQuery.copyFrom(query);
        return new QueryPair(null, completionQuery);
    }

    /**
     * Splits the query for a data source with capabilities SQL.
     * If the query contains scalar functions, then the query is split as if data source capabilities
     * are NONE. If the query does not contain scalar functions, then the data source query contains
     * most of the operations.
     * Because SQL cannot handle pivoting, special care needs to be taken if the query includes a
     * pivot operation. The aggregation operation required for pivoting is passed to the data source
     * query. We make use of this, with some implementation tricks. See implementation comments.
     *
     * @param query The original query.
     * @return The split query.
     */
    private static QueryPair splitSQL(Query query) {
        // Situations we currently do not support good splitting of:
        // - Queries with scalar functions.
        // - Queries with pivot that also contain labels or formatting on aggregation columns.
        if(!query.getAllScalarFunctionsColumns().isEmpty()
                || (query.hasPivot()
                && ((query.hasUserFormatOptions() &&
                !query.getUserFormatOptions().getAggregationColumns().isEmpty())
                || (query.hasLabels() && !query.getLabels().getAggregationColumns().isEmpty())))) {
            Query completionQuery = new Query();
            completionQuery.copyFrom(query);
            return new QueryPair(new Query(), completionQuery);
        }

        Query dataSourceQuery = new Query();
        Query completionQuery = new Query();

        // sql supports select, where, sort, group, limit, offset.
        // The library further supports pivot.
        if(query.hasPivot()) {
            // Make the pivot columns additional grouping columns, and handle the
            // transformation later.

            List<AbstractColumn> pivotColumns = query.getPivot().getColumns();

            dataSourceQuery.copyFrom(query);
            dataSourceQuery.setPivot(null);
            dataSourceQuery.setSort(null);
            dataSourceQuery.setOptions(null);
            dataSourceQuery.setLabels(null);
            dataSourceQuery.setUserFormatOptions(null);

            try {
                dataSourceQuery.setRowSkipping(0);
                dataSourceQuery.setRowLimit(-1);
                dataSourceQuery.setRowOffset(0);
            }
            catch(InvalidQueryException e) {
                // Should not happen.
            }

            // Let the data source group by all grouping/pivoting columns, and let it
            // select all selection/pivoting columns, e.g., SELECT A, max(B) GROUP BY A PIVOT C turns
            // into SELECT A, max(B), C GROUP BY A, C

            List<AbstractColumn> newGroupColumns = Lists.newArrayList();
            List<AbstractColumn> newSelectionColumns = Lists.newArrayList();
            if(dataSourceQuery.hasGroup()) {
                // Same logic applies here, no calculated columns and no aggregations.
                newGroupColumns.addAll(dataSourceQuery.getGroup().getColumns());
            }
            newGroupColumns.addAll(pivotColumns);
            if(dataSourceQuery.hasSelection()) {
                newSelectionColumns.addAll(dataSourceQuery.getSelection().getColumns());
            }
            newSelectionColumns.addAll(pivotColumns);
            QueryGroup group = new QueryGroup();
            for(AbstractColumn col : newGroupColumns) {
                group.addColumn(col);
            }
            dataSourceQuery.setGroup(group);
            QuerySelection selection = new QuerySelection();
            for(AbstractColumn col : newSelectionColumns) {
                selection.addColumn(col);
            }
            dataSourceQuery.setSelection(selection);

            // Build the completion query to group by the grouping columns. Because an aggregation is
            // required, make a dummy aggregation on the original column by which the aggregation is
            // required.
            // This original column must be unique for a given set of values for the grouping/pivoting
            // columns so any aggregation operation out of MIN, MAX, AVG will return the value
            // itself and will not aggregate anything. The example from before,
            // SELECT A, max(B) GROUP BY A PIVOT C turns into SELECT A, min(max-B) GROUP BY A PIVOT C

            completionQuery.copyFrom(query);
            completionQuery.setFilter(null);

            QuerySelection completionSelection = new QuerySelection();
            List<AbstractColumn> originalSelectedColumns =
                    query.getSelection().getColumns();
            for(int i = 0; i < originalSelectedColumns.size(); i++) {
                AbstractColumn column = originalSelectedColumns.get(i);
                if(query.getGroup().getColumns().contains(column)) {
                    completionSelection.addColumn(column);
                }
                else { // Must be an aggregation column if doesn't appear in the grouping.
                    // The id here is the id generated by the data source for the column containing
                    // the aggregated data, e.g., max-B.
                    String id = column.getId();
                    // MIN is chosen arbitrarily, because there will be exactly one.
                    completionSelection.addColumn(
                            new AggregationColumn(new SimpleColumn(id), AggregationType.MIN));
                }
            }

            completionQuery.setSelection(completionSelection);
        }
        else {
            // When there is no pivoting, sql does everything (except skipping, options, labels, format).
            dataSourceQuery.copyFrom(query);
            dataSourceQuery.setOptions(null);
            completionQuery.setOptions(query.getOptions());
            try {
                // If there is skipping pagination should be done in the completion query
                if(query.hasRowSkipping()) {
                    dataSourceQuery.setRowSkipping(0);
                    dataSourceQuery.setRowLimit(-1);
                    dataSourceQuery.setRowOffset(0);

                    completionQuery.copyRowSkipping(query);
                    completionQuery.copyRowLimit(query);
                    completionQuery.copyRowOffset(query);
                }
                if(query.hasLabels()) {
                    dataSourceQuery.setLabels(null);
                    QueryLabels labels = query.getLabels();
                    QueryLabels newLabels = new QueryLabels();
                    for(AbstractColumn column : labels.getColumns()) {
                        newLabels.addLabel(new SimpleColumn(column.getId()), labels.getLabel(column));
                    }
                    completionQuery.setLabels(newLabels);
                }
                if(query.hasUserFormatOptions()) {
                    dataSourceQuery.setUserFormatOptions(null);
                    QueryFormat formats = query.getUserFormatOptions();
                    QueryFormat newFormats = new QueryFormat();
                    for(AbstractColumn column : formats.getColumns()) {
                        newFormats.addPattern(new SimpleColumn(column.getId()), formats.getPattern(column));
                    }
                    completionQuery.setUserFormatOptions(newFormats);
                }
            }
            catch(InvalidQueryException e) {
                // Should not happen.
            }
        }
        return new QueryPair(dataSourceQuery, completionQuery);
    }

    /**
     * Splits the query for a data source with capabilities SORT_AND_PAGINATION.
     * Algorithm: if the query has filter, grouping, pivoting or skipping requirements the query is
     * split as in the NONE case.
     * If the query does not have filter, grouping, pivoting or skipping the data source query
     * receives any sorting or pagination requirements and the completion query receives
     * any selection requirements.
     *
     * @param query The query to split.
     * @return The split query.
     */
    private static QueryPair splitSortAndPagination(Query query) {
        if(!query.getAllScalarFunctionsColumns().isEmpty()) {
            Query completionQuery = new Query();
            completionQuery.copyFrom(query);
            return new QueryPair(new Query(), completionQuery);
        }

        Query dataSourceQuery = new Query();
        Query completionQuery = new Query();
        if(query.hasFilter() || query.hasGroup() || query.hasPivot()) {
            // The query is copied to the completion query.
            completionQuery.copyFrom(query);
        }
        else {
            // The execution order of the 3 relevant operators is:
            // sort -> skip -> paginate (limit and offset).
            // Skipping is not a possible data source capability, Therefore:
            // 1. Sorting can be performed in the data source query.
            // 2. Pagination should be performed in the data source query IFF skipping
            //    isn't stated in the original query.
            dataSourceQuery.setSort(query.getSort());
            if(query.hasRowSkipping()) {
                completionQuery.copyRowSkipping(query);
                completionQuery.copyRowLimit(query);
                completionQuery.copyRowOffset(query);
            }
            else {
                dataSourceQuery.copyRowLimit(query);
                dataSourceQuery.copyRowOffset(query);
            }

            completionQuery.setSelection(query.getSelection());
            completionQuery.setOptions(query.getOptions());
            completionQuery.setLabels(query.getLabels());
            completionQuery.setUserFormatOptions(query.getUserFormatOptions());
        }
        return new QueryPair(dataSourceQuery, completionQuery);
    }

    /**
     * Splits the query for a data source with capabilities SELECT.
     * Algorithm: the data source query receives any select operation from the query, however the
     * completion query also receives selection to properly post-process the result.
     *
     * @param query The query to split.
     * @return The split query.
     */
    private static QueryPair splitSelect(Query query) {
        Query dataSourceQuery = new Query();
        Query completionQuery = new Query();
        if(query.getSelection() != null) {
            QuerySelection selection = new QuerySelection();
            for(String simpleColumnId : query.getAllColumnIds()) {
                selection.addColumn(new SimpleColumn(simpleColumnId));
            }
            // Column selection can be empty. For example, for query "SELECT 1".
            dataSourceQuery.setSelection(selection);
        }

        completionQuery.copyFrom(query);
        return new QueryPair(dataSourceQuery, completionQuery);
    }
}
TOP

Related Classes of com.google.visualization.datasource.QuerySplitter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.