Package com.google.visualization.datasource.query.engine

Source Code of com.google.visualization.datasource.query.engine.QueryEngine

// 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.query.engine;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.visualization.datasource.base.ReasonType;
import com.google.visualization.datasource.base.TypeMismatchException;
import com.google.visualization.datasource.base.Warning;
import com.google.visualization.datasource.datatable.ColumnDescription;
import com.google.visualization.datasource.datatable.DataTable;
import com.google.visualization.datasource.datatable.TableCell;
import com.google.visualization.datasource.datatable.TableRow;
import com.google.visualization.datasource.datatable.ValueFormatter;
import com.google.visualization.datasource.datatable.value.Value;
import com.google.visualization.datasource.query.AbstractColumn;
import com.google.visualization.datasource.query.AggregationColumn;
import com.google.visualization.datasource.query.ColumnLookup;
import com.google.visualization.datasource.query.DataTableColumnLookup;
import com.google.visualization.datasource.query.GenericColumnLookup;
import com.google.visualization.datasource.query.Query;
import com.google.visualization.datasource.query.QueryFilter;
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.QueryPivot;
import com.google.visualization.datasource.query.QuerySelection;
import com.google.visualization.datasource.query.QuerySort;
import com.google.visualization.datasource.query.ScalarFunctionColumn;
import com.google.visualization.datasource.query.SimpleColumn;

import com.ibm.icu.util.ULocale;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;

/**
* A collection of static methods that perform the operations involved in executing a query,
* i.e., selection, sorting, paging (limit and offset), grouping and pivoting, filtering, skipping,
* applying labels, and custom formatting. This also takes care of calculated columns.
* This also takes care of scalar function columns.
*
* @author Yoah B.D.
* @author Yonatan B.Y.
* @author Liron L.
*/
public final class QueryEngine {

  /**
   * Empty private constructor, to prevent initialization.
   */
  private QueryEngine() {}

  /**
   * Creates the columns structure for a new table after pivoting and grouping.
   *
   * @param groupByColumnIds The column ids to group by. This is used for the
   *     ColumnDescriptions of the first columns in the table.
   * @param columnTitles The ColumnTitles of the aggregation columns in the table, i.e., columns
   *     that are composed of pivot values and aggregations.
   * @param original The original table, from which to get the original ColumnDescriptions.
   * @param scalarFunctionColumnTitles The scalar function column titles. i.e.,
   *     columns that are composed of pivot values and scalar function column.
   *
   * @return The new TableDescription.
   */
  private static DataTable createDataTable(
      List<String> groupByColumnIds, SortedSet<ColumnTitle> columnTitles,
      DataTable original, List<ScalarFunctionColumnTitle> scalarFunctionColumnTitles) {
    DataTable result = new DataTable();
    for (String groupById : groupByColumnIds) {
      result.addColumn(original.getColumnDescription(groupById));
    }
    for (ColumnTitle colTitle : columnTitles) {
      result.addColumn(colTitle.createColumnDescription(original));
    }

    for (ScalarFunctionColumnTitle scalarFunctionColumnTitle : scalarFunctionColumnTitles) {
      result.addColumn(scalarFunctionColumnTitle.createColumnDescription(original));
    }
    return result;
  }

  /**
   * Returns the data that is the result of executing the query. The query is validated against the
   * data table before execution and an InvalidQueryException is thrown if it is invalid.
   * This function may change the given DataTable.
   *
   * @param query The query.
   * @param table The table to execute the query on.
   *
   * @return The data that is the result of executing the query.
   */
  public static DataTable executeQuery(Query query, DataTable table, ULocale locale) {
    ColumnIndices columnIndices = new ColumnIndices();
    List<ColumnDescription> columnsDescription = table.getColumnDescriptions();
    for (int i = 0; i < columnsDescription.size(); i++) {
      columnIndices.put(new SimpleColumn(columnsDescription.get(i).getId()), i);
    }

    // A map of column lookups by their list of pivot values. This is utilized in
    // the grouping and pivoting queries.
    TreeMap<List<Value>, ColumnLookup> columnLookups =
        new TreeMap<List<Value>, ColumnLookup>(GroupingComparators.VALUE_LIST_COMPARATOR);
    try {
      table = performFilter(table, query);
      table = performGroupingAndPivoting(table, query, columnIndices, columnLookups);
      table = performSort(table, query, locale);
      table = performSkipping(table, query);
      table = performPagination(table, query);

      AtomicReference<ColumnIndices> columnIndicesReference =
        new AtomicReference<ColumnIndices>(columnIndices);
      table = performSelection(table, query, columnIndicesReference, columnLookups);
      columnIndices = columnIndicesReference.get();

      table = performLabels(table, query, columnIndices);
      table = performFormatting(table, query, columnIndices, locale);
    } catch (TypeMismatchException e) {
      // Should not happen.
    }
    return table;
  }

  /**
   * Returns a table consisted of a subset of rows of the input table.
   * We select the first out of every k rows in the table according to the
   * skipping value in the query.
   * If there is no need to do anything, returns the original table.
   *
   * @param table The original table.
   * @param query The query.
   *
   * @return The skipped table, or the original if no skipping is needed.
   */
  private static DataTable performSkipping(DataTable table, Query query)
      throws TypeMismatchException {
    int rowSkipping = query.getRowSkipping();
   
    // Return the original table if no skipping is needed
    if (rowSkipping <= 1) {
      return table;
    }

    // Add the first out of every k rows of the original table to TableRow
    int numRows = table.getNumberOfRows();
    List<TableRow> relevantRows = new ArrayList<TableRow>();
    for (int rowIndex = 0; rowIndex < numRows; rowIndex += rowSkipping) {
        relevantRows.add(table.getRows().get(rowIndex));
    }
   
    // Create a table out of the TableRow array
    DataTable newTable = new DataTable();
    newTable.addColumns(table.getColumnDescriptions());
    newTable.addRows(relevantRows);
   
    return newTable;
  }

  /**
   * Returns a paginated table, based on the row limit and offset parameters.
   * If there is no need to do anything, returns the original table.
   *
   * @param table The original table.
   * @param query The query.
   *
   * @return The paginated table, or the original if no pagination is needed.
   */
  private static DataTable performPagination(DataTable table, Query query)
      throws TypeMismatchException {
    int rowOffset = query.getRowOffset();
    int rowLimit = query.getRowLimit();

    // Return the original table if no pagination is needed
    if (((rowLimit == -1) || (table.getRows().size() <= rowLimit)) && (rowOffset == 0)) {
      return table;
    }
    int numRows = table.getNumberOfRows();
    int fromIndex = Math.max(0, rowOffset);
    int toIndex = (rowLimit == -1) ? numRows : Math.min(numRows, rowOffset + rowLimit);

    List<TableRow> relevantRows = table.getRows().subList(fromIndex, toIndex);
    DataTable newTable = new DataTable();
    newTable.addColumns(table.getColumnDescriptions());
    newTable.addRows(relevantRows);

    if (toIndex < numRows) { // Data truncated
      Warning warning = new Warning(ReasonType.DATA_TRUNCATED, "Data has been truncated due to user"
          + "request (LIMIT in query)");
      newTable.addWarning(warning);
    }

    return newTable;
  }

  /**
   * Returns a table sorted according to the query's sort.
   * The returned table has the same rows as the original table.
   *
   * @param table The table to sort.
   * @param query The query.
   *
   * @return The sorted table.
   */
  private static DataTable performSort(DataTable table, Query query, ULocale locale) {
    if (!query.hasSort()) {
      return table;
    }
    QuerySort sortBy = query.getSort();
    // A table description column lookup is enough because sorting by a column
    // that has multiple matching columns after pivoting is impossible. For example,
    // it is impossible to sort by an aggregation column when there is a pivot.
    DataTableColumnLookup columnLookup = new DataTableColumnLookup(table);
    TableRowComparator comparator = new TableRowComparator(sortBy, locale, columnLookup);
    Collections.sort(table.getRows(), comparator);
    return table;
  }

  /**
   * Returns a table that has only the rows from the given table that match the filter
   * provided by a query.
   *
   * @param table The table to filter.
   * @param query The query.
   *
   * @return The filtered table.
   */
  private static DataTable performFilter(DataTable table, Query query)
      throws TypeMismatchException {
    if (!query.hasFilter()) {
      return table;
    }

    List<TableRow> newRowList = Lists.newArrayList();
    QueryFilter filter = query.getFilter();
    for (TableRow inputRow : table.getRows()) {
      if (filter.isMatch(table, inputRow)) {
        newRowList.add(inputRow);
      }
    }
    table.setRows(newRowList);
    return table;
  }

  /**
   * Returns a table that has only the columns from the given table that are specified by
   * the query.
   *
   * @param table The table from which to select.
   * @param query The query.
   * @param columnIndicesReference A reference to a ColumnIndices instance, so that
   *     this function can change the internal ColumnIndices.
   * @param columnLookups A map of column lookups by their list of pivot values.
   *
   * @return The table with selected columns only.
   */
  private static DataTable performSelection(DataTable table, Query query,
      AtomicReference<ColumnIndices> columnIndicesReference,
      Map<List<Value>, ColumnLookup> columnLookups) throws TypeMismatchException {
    if (!query.hasSelection()) {
      return table;
    }

    ColumnIndices columnIndices = columnIndicesReference.get();

    List<AbstractColumn> selectedColumns = query.getSelection().getColumns();
    List<Integer> selectedIndices = Lists.newArrayList();

    // Build the new table description, and update columnIndices
    List<ColumnDescription> oldColumnDescriptions = table.getColumnDescriptions();
    List<ColumnDescription> newColumnDescriptions = Lists.newArrayList();
    ColumnIndices newColumnIndices = new ColumnIndices();
    int currIndex = 0;
    for (AbstractColumn col : selectedColumns) {
      // If the query has pivoting, then AggregationColumns in the SELECT are
      // discarded, since they are only there to control the pivoting.
      List<Integer> colIndices = columnIndices.getColumnIndices(col);
      selectedIndices.addAll(colIndices);
      // If the selected column does not exist in the columnIndices, then it is
      // a scalar function column that was not in the original table, and was not
      // calculated in the grouping and pivoting stage.
      if (colIndices.size() == 0) {
        newColumnDescriptions.add(new ColumnDescription(col.getId(),
            col.getValueType(table),
            ScalarFunctionColumnTitle.getColumnDescriptionLabel(table, col)));
        newColumnIndices.put(col, currIndex++);
      } else {
        for (int colIndex : colIndices) {
          newColumnDescriptions.add(oldColumnDescriptions.get(colIndex));
          newColumnIndices.put(col, currIndex++);
        }
      }
    }
    columnIndices = newColumnIndices;
    columnIndicesReference.set(columnIndices);

    DataTable result = new DataTable();
    result.addColumns(newColumnDescriptions);

    // Calculate the values in the data table rows.
    for (TableRow sourceRow : table.getRows()) {
      TableRow newRow = new TableRow();
      for (AbstractColumn col : selectedColumns) {
        boolean wasFound = false;
        Set<List<Value>> pivotValuesSet = columnLookups.keySet();
        for (List<Value> values : pivotValuesSet) {
          // If the current column-lookup contains the current column and it is
          // either a column that contains aggregations or a column that
          // contains only group-by columns and was not yet found, get its value
          // in the current row. Otherwise continue. If the column contains
          // only group-by columns it should appear only once, even though
          // it may appear in many column lookups.
          if (columnLookups.get(values).containsColumn(col)
              && ((col.getAllAggregationColumns().size() != 0) || !wasFound)) {
            wasFound = true;
            newRow.addCell(sourceRow.getCell(columnLookups.get(values).getColumnIndex(col)));
          }
        }
        // If the column was not found in any of the column lookups
        // calculate its value (e.g., scalar function column that was not
        // calculated in a previous stage).
        if (!wasFound) {
          DataTableColumnLookup lookup = new DataTableColumnLookup(table);
          newRow.addCell(col.getCell(lookup, sourceRow));
        }
      }
      result.addRow(newRow);
    }
    return result;
  }

  /**
   * Returns true if the query has aggregation columns and the table is not
   * empty.
   *
   * @param query The given query.
   *
   * @return true if the query has aggregation columns and the table is not
   *     empty.
   */
  private static boolean queryHasAggregation(Query query) {
    return (query.hasSelection()
        && !query.getSelection().getAggregationColumns().isEmpty());
  }

  /**
   * Returns the result of performing the grouping (and pivoting) operations
   * on the given table, using the information provided in the query's group
   * and pivot.
   *
   * The new table generated has columns as follows where A is the number of group-by columns,
   * B is the number of combinations of values of pivot-by columns, and X is the number of
   * aggregations requested:
   * - Columns 1..A are the original group-by columns, in the order they are
   * given in the group-by list.
   * - Columns (A+1)..B are pivot and aggregation columns, where each
   * column's id is composed of values of the pivot-by columns in the
   * original table, and an aggregation column, with separators between them.
   *
   * Note that the aggregations requested can be all on the same
   * aggregation column or on different aggregation columns. To this
   * mechanism, it doesn't matter.
   *
   * There is a row for each combination of the values of the group-by columns.
   *
   * The value in the cell at row X and column Y is the result of the requested
   * aggregation type described in Y on the set of values in the aggregation
   * column (also described in Y) for which the values of the group-by columns
   * are as determined by X and the values of the pivot-by columns are as
   * determined by the column.
   *
   * @param table The original table.
   * @param query The query.
   * @param columnIndices A map, in which this method sets the indices
   *     of the new columns, if grouping is performed, and then any
   *     previous values in it are cleared. If grouping is not performed, it is
   *     left as is.
   *
   * @return The new table, after grouping and pivoting was performed.
   */
  private static DataTable performGroupingAndPivoting(DataTable table, Query query,
      ColumnIndices columnIndices, TreeMap<List<Value>, ColumnLookup> columnLookups)
      throws TypeMismatchException {
    if (!queryHasAggregation(query) || (table.getNumberOfRows() == 0)) {
      return table;
    }
    QueryGroup group = query.getGroup();
    QueryPivot pivot = query.getPivot();
    QuerySelection selection = query.getSelection();

    List<String> groupByIds = Lists.newArrayList();
    if (group != null) {
      groupByIds = group.getColumnIds();
    }

    List<String> pivotByIds = Lists.newArrayList();
    if (pivot != null) {
      pivotByIds = pivot.getColumnIds(); // contained in groupByIds
    }

    List<String> groupAndPivotIds = Lists.newArrayList(groupByIds);
    groupAndPivotIds.addAll(pivotByIds);

    List<AggregationColumn> tmpColumnAggregations = selection.getAggregationColumns();
    List<ScalarFunctionColumn> selectedScalarFunctionColumns = selection.getScalarFunctionColumns();
   
    // Remove duplicates from tmpColumnAggregations, creating columnAggregations:
    List<AggregationColumn> columnAggregations =
      Lists.newArrayListWithExpectedSize(tmpColumnAggregations.size());
    for (AggregationColumn aggCol : tmpColumnAggregations) {
      if (!columnAggregations.contains(aggCol)) {
        columnAggregations.add(aggCol);
      }
    }
   
    List<String> aggregationIds = Lists.newArrayList();
    for (AggregationColumn col : columnAggregations) {
      aggregationIds.add(col.getAggregatedColumn().getId());
    }

    List<ScalarFunctionColumn> groupAndPivotScalarFunctionColumns = Lists.newArrayList();
    if (group != null) {
      groupAndPivotScalarFunctionColumns.addAll(group.getScalarFunctionColumns());
    }
    if (pivot != null) {
      groupAndPivotScalarFunctionColumns.addAll(pivot.getScalarFunctionColumns());
    }

    List<ColumnDescription> newColumnDescriptions = Lists.newArrayList();
    newColumnDescriptions.addAll(table.getColumnDescriptions());

    // Add to the table description the scalar function columns included in the
    // group and pivot. The groups of rows are defined according to the
    // values of those columns, and so it is necessary to add them before the
    // calculations of the groups, pivots and aggregations.
    for (ScalarFunctionColumn column : groupAndPivotScalarFunctionColumns) {
      newColumnDescriptions.add(new ColumnDescription(column.getId(),
          column.getValueType(table),
          ScalarFunctionColumnTitle.getColumnDescriptionLabel(table, column)));
    }

    DataTable tempTable = new DataTable();
    tempTable.addColumns(newColumnDescriptions);

    // Calculate the values of the added scalar function columns in each row.
    DataTableColumnLookup lookup = new DataTableColumnLookup(table);
    for (TableRow sourceRow : table.getRows()) {
      TableRow newRow = new TableRow();
      for (TableCell sourceCell : sourceRow.getCells()) {
        newRow.addCell(sourceCell);
      }
      for (ScalarFunctionColumn column : groupAndPivotScalarFunctionColumns) {
        newRow.addCell(new TableCell(column.getValue(lookup, sourceRow)));
      }
      try {
        tempTable.addRow(newRow);
      } catch (TypeMismatchException e) {
        // Should not happen, given that the original table is OK.
      }
    }
    table = tempTable;

    // Calculate the aggregations.
    TableAggregator aggregator = new TableAggregator(groupAndPivotIds,
        Sets.newHashSet(aggregationIds), table);
    Set<AggregationPath> paths = aggregator.getPathsToLeaves();

    // These variables will hold the "titles" of the rows and columns.
    // They are TreeSets because their order matters.
    SortedSet<RowTitle> rowTitles =
        Sets.newTreeSet(GroupingComparators.ROW_TITLE_COMPARATOR);
    SortedSet<ColumnTitle> columnTitles = Sets.newTreeSet(
        GroupingComparators.getColumnTitleDynamicComparator(columnAggregations));

    // A tree set containing all pivot value lists (the set is for the
    // uniqueness and the tree for the order).
    TreeSet<List<Value>> pivotValuesSet =
        Sets.newTreeSet(GroupingComparators.VALUE_LIST_COMPARATOR);
    // This MetaTable holds all the data in the table, this data is then
    // dumped into the real table.
    MetaTable metaTable = new MetaTable();
    for (AggregationColumn columnAggregation : columnAggregations) {
      for (AggregationPath path : paths) {

        // A ColumnTitle is composed of all the values for the pivot-by
        // columns, and a ColumnAggregation. That is why it is necessary to iterate over all
        // ColumnAggregations and create a ColumnTitle for each one.
        List<Value> originalValues = path.getValues();

        // Separate originalValues into the rowValues and columnValues. The
        // rowValues are the values of the group-by columns and the columnValues
        // are the values of the pivot-by columns.
        List<Value> rowValues = originalValues.subList(0, groupByIds.size());
        RowTitle rowTitle = new RowTitle(rowValues);
        rowTitles.add(rowTitle);

        List<Value> columnValues = originalValues.subList(groupByIds.size(), originalValues.size());
        pivotValuesSet.add(columnValues);

        ColumnTitle columnTitle = new ColumnTitle(columnValues,
            columnAggregation, (columnAggregations.size() > 1));
        columnTitles.add(columnTitle);
        metaTable.put(rowTitle, columnTitle, new TableCell(aggregator.getAggregationValue(path,
            columnAggregation.getAggregatedColumn().getId(),
            columnAggregation.getAggregationType())));
      }
    }

    // Create the scalar function column titles for the scalar function columns
    // that contain aggregations.
    List<ScalarFunctionColumnTitle> scalarFunctionColumnTitles =
        Lists.newArrayList();
    for (ScalarFunctionColumn scalarFunctionColumn :
        selectedScalarFunctionColumns) {
      if (scalarFunctionColumn.getAllAggregationColumns().size() != 0) {
        for (List<Value> columnValues : pivotValuesSet) {
          scalarFunctionColumnTitles.add(new ScalarFunctionColumnTitle(columnValues,
              scalarFunctionColumn));
        }
      }
    }

    // Create the new table description.
    DataTable result = createDataTable(groupByIds, columnTitles, table, scalarFunctionColumnTitles);
    List<ColumnDescription> colDescs = result.getColumnDescriptions();

    // Fill the columnIndices and columnLookups parameters for the group-by
    // columns and the aggregation columns.
    columnIndices.clear();
    int columnIndex = 0;
    if (group != null) {
      List<Value> empytListOfValues = Lists.newArrayList();
      columnLookups.put(empytListOfValues, new GenericColumnLookup());
      for (AbstractColumn column : group.getColumns()) {
        columnIndices.put(column, columnIndex);
        if (!(column instanceof ScalarFunctionColumn)) {
          ((GenericColumnLookup) columnLookups.get(empytListOfValues)).put(column, columnIndex);
          for (List<Value> columnValues : pivotValuesSet) {
            if (!columnLookups.containsKey(columnValues)) {
              columnLookups.put(columnValues, new GenericColumnLookup());
            }
            ((GenericColumnLookup) columnLookups.get(columnValues)).put(column, columnIndex);
          }
        }
        columnIndex++;
      }
    }

    for (ColumnTitle title : columnTitles) {
      columnIndices.put(title.aggregation, columnIndex);
      List<Value> values = title.getValues();
      if (!columnLookups.containsKey(values)) {
        columnLookups.put(values, new GenericColumnLookup());
      }
      ((GenericColumnLookup) columnLookups.get(values)).put(title.aggregation, columnIndex);
      columnIndex++;
    }

    // Dump the data from the metaTable to the result DataTable.
    for (RowTitle rowTitle : rowTitles) {
      TableRow curRow = new TableRow();
      // Add the group-by columns cells.
      for (Value v : rowTitle.values) {
        curRow.addCell(new TableCell(v));
      }
      Map<ColumnTitle, TableCell> rowData = metaTable.getRow(rowTitle);
      int i = 0;
      // Add the aggregation columns cells.
      for (ColumnTitle colTitle : columnTitles) {
        TableCell cell = rowData.get(colTitle);
        curRow.addCell((cell != null) ? cell : new TableCell(
            Value.getNullValueFromValueType(colDescs.get(i + rowTitle.values.size()).getType())));
        i++;
      }
      // Add the scalar function columns cells.
      for (ScalarFunctionColumnTitle columnTitle : scalarFunctionColumnTitles) {
        curRow.addCell(new TableCell(columnTitle.scalarFunctionColumn.
            getValue(columnLookups.get(columnTitle.getValues()), curRow)));
      }
      result.addRow(curRow);
    }

    // Fill the columnIndices and columnLookups parameters for the scalar
    // function column titles. This must be done after the calculation of the values
    // in the scalar function column cells, or else the scalar function columns
    // will not calculate their value recursively, but return the current value.
    // See the logic of the getValue() method in ScalarFunctionColumn.
    for (ScalarFunctionColumnTitle scalarFunctionColumnTitle
        : scalarFunctionColumnTitles) {
      columnIndices.put(scalarFunctionColumnTitle.scalarFunctionColumn,
          columnIndex);
      List<Value> values = scalarFunctionColumnTitle.getValues();
      if (!columnLookups.containsKey(values)) {
        columnLookups.put(values, new GenericColumnLookup());
      }
      ((GenericColumnLookup) columnLookups.get(values)).put(
          scalarFunctionColumnTitle.scalarFunctionColumn, columnIndex);
      columnIndex++;
    }

    return result;
  }

  /**
   * Apply labels to columns as specified in the user query.
   * If a column is specified in the query, but is not part of the data table,
   * this is still a valid situation, and the "invalid" column id is ignored.
   *
   * @param table The original table.
   * @param query The query.
   * @param columnIndices The map of columns to indices in the table.
   *
   * @return The table with labels applied.
   */
  private static DataTable performLabels(DataTable table, Query query,
      ColumnIndices columnIndices) {

    if (!query.hasLabels()) {
      return table;
    }

    QueryLabels labels = query.getLabels();

    List<ColumnDescription> columnDescriptions = table.getColumnDescriptions();

    for (AbstractColumn column : labels.getColumns()) {
      String label = labels.getLabel(column);
      List<Integer> indices = columnIndices.getColumnIndices(column);
      if (indices.size() == 1) {
        columnDescriptions.get(indices.get(0)).setLabel(label);
      } else {
        String columnId = column.getId(); // Without pivot values.
        for (int i : indices) {
          ColumnDescription colDesc = columnDescriptions.get(i);
          String colDescId = colDesc.getId(); // Includes pivot values.
          String specificLabel =
              colDescId.substring(0, colDescId.length() - columnId.length()) + label;
          columnDescriptions.get(i).setLabel(specificLabel);
        }

      }
    }
    return table;
  }

  /**
   * Add column formatters according to a given patterns list. Namely,
   * a visualization gadget can send a map of patterns by column ids. The following
   * method builds the appropriate formatters for these patterns.
   * An illegal pattern is recorded for later sending of a warning.
   *
   * @param table The original table.
   * @param query The query.
   * @param columnIndices The map of columns to indices in the table.
   * @param locale The locale by which to format.
   *
   * @return The table with formatting applied.
   */
  private static DataTable performFormatting(DataTable table, Query query,
      ColumnIndices columnIndices, ULocale locale) {
    if (!query.hasUserFormatOptions()) {
      return table;
    }

    QueryFormat queryFormat = query.getUserFormatOptions();
    List<ColumnDescription> columnDescriptions = table.getColumnDescriptions();
    Map<Integer, ValueFormatter> indexToFormatter = Maps.newHashMap();
    for (AbstractColumn col : queryFormat.getColumns()) {
      String pattern = queryFormat.getPattern(col);
      List<Integer> indices = columnIndices.getColumnIndices(col);
      boolean allSucceeded = true;
      for (int i : indices) {
        ColumnDescription colDesc = columnDescriptions.get(i);
        ValueFormatter f = ValueFormatter.createFromPattern(colDesc.getType(), pattern, locale);
        if (f == null) {
          allSucceeded = false;
        } else {
          indexToFormatter.put(i, f);
          table.getColumnDescription(i).setPattern(pattern); // May override datasource pattern.
        }
      }
      if (!allSucceeded) {
        Warning warning = new Warning(ReasonType.ILLEGAL_FORMATTING_PATTERNS,
            "Illegal formatting pattern: " + pattern + " requested on column: " + col.getId());
        table.addWarning(warning);
      }
    }

    for (TableRow row : table.getRows()) {
      for (int col : indexToFormatter.keySet()) {
        TableCell cell = row.getCell(col);
        Value value = cell.getValue();
        ValueFormatter formatter = indexToFormatter.get(col);
        String formattedValue = formatter.format(value);
        cell.setFormattedValue(formattedValue);
      }
    }
    return table;
  }
}
TOP

Related Classes of com.google.visualization.datasource.query.engine.QueryEngine

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.