All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.druid.sql.calcite.table.RowSignature Maven / Gradle / Ivy

There is a newer version: 0.12.3
Show newest version
/*
 * Licensed to Metamarkets Group Inc. (Metamarkets) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. Metamarkets licenses this file
 * to you 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 io.druid.sql.calcite.table;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.druid.java.util.common.IAE;
import io.druid.java.util.common.ISE;
import io.druid.java.util.common.Pair;
import io.druid.query.ordering.StringComparator;
import io.druid.query.ordering.StringComparators;
import io.druid.segment.column.Column;
import io.druid.segment.column.ValueType;
import io.druid.sql.calcite.expression.RowExtraction;
import io.druid.sql.calcite.planner.Calcites;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlCollation;
import org.apache.calcite.sql.type.SqlTypeName;

import java.util.List;
import java.util.Map;

/**
 * Type signature for a row in a Druid dataSource ("DruidTable") or query result. Rows have an ordering and every
 * column has a defined type. This is a little bit of a fiction in the Druid world (where rows do not _actually_ have
 * well defined types) but we do impose types for the SQL layer.
 */
public class RowSignature
{
  private final Map columnTypes;
  private final List columnNames;

  private RowSignature(final List> columnTypeList)
  {
    final Map columnTypes0 = Maps.newHashMap();
    final ImmutableList.Builder columnNamesBuilder = ImmutableList.builder();

    int i = 0;
    for (Pair pair : columnTypeList) {
      final ValueType existingType = columnTypes0.get(pair.lhs);
      if (existingType != null && existingType != pair.rhs) {
        throw new IAE("Column[%s] has conflicting types [%s] and [%s]", pair.lhs, existingType, pair.rhs);
      }

      columnTypes0.put(pair.lhs, pair.rhs);
      columnNamesBuilder.add(pair.lhs);
    }

    this.columnTypes = ImmutableMap.copyOf(columnTypes0);
    this.columnNames = columnNamesBuilder.build();
  }

  public static Builder builder()
  {
    return new Builder();
  }

  public ValueType getColumnType(final String name)
  {
    return columnTypes.get(name);
  }

  /**
   * Returns the rowOrder for this signature, which is the list of column names in row order.
   *
   * @return row order
   */
  public List getRowOrder()
  {
    return columnNames;
  }

  /**
   * Return the "natural" {@link StringComparator} for an extraction from this row signature. This will be a
   * lexicographic comparator for String types and a numeric comparator for Number types.
   *
   * @param rowExtraction extraction from this kind of row
   *
   * @return natural comparator
   */
  public StringComparator naturalStringComparator(final RowExtraction rowExtraction)
  {
    Preconditions.checkNotNull(rowExtraction, "rowExtraction");
    if (rowExtraction.getExtractionFn() != null
        || getColumnType(rowExtraction.getColumn()) == ValueType.STRING) {
      return StringComparators.LEXICOGRAPHIC;
    } else {
      return StringComparators.NUMERIC;
    }
  }

  /**
   * Returns a Calcite RelDataType corresponding to this row signature.
   *
   * @param typeFactory factory for type construction
   *
   * @return Calcite row type
   */
  public RelDataType getRelDataType(final RelDataTypeFactory typeFactory)
  {
    final RelDataTypeFactory.FieldInfoBuilder builder = typeFactory.builder();
    for (final String columnName : columnNames) {
      final ValueType columnType = getColumnType(columnName);
      final RelDataType type;

      if (Column.TIME_COLUMN_NAME.equals(columnName)) {
        type = typeFactory.createSqlType(SqlTypeName.TIMESTAMP);
      } else {
        switch (columnType) {
          case STRING:
            // Note that there is no attempt here to handle multi-value in any special way. Maybe one day...
            type = typeFactory.createTypeWithCharsetAndCollation(
                typeFactory.createSqlType(SqlTypeName.VARCHAR),
                Calcites.defaultCharset(),
                SqlCollation.IMPLICIT
            );
            break;
          case LONG:
            type = typeFactory.createSqlType(SqlTypeName.BIGINT);
            break;
          case FLOAT:
            type = typeFactory.createSqlType(SqlTypeName.FLOAT);
            break;
          case COMPLEX:
            // Loses information about exactly what kind of complex column this is.
            type = typeFactory.createSqlType(SqlTypeName.OTHER);
            break;
          default:
            throw new ISE("WTF?! valueType[%s] not translatable?", columnType);
        }
      }

      builder.add(columnName, type);
    }

    return builder.build();
  }

  @Override
  public boolean equals(Object o)
  {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }

    RowSignature that = (RowSignature) o;

    if (columnTypes != null ? !columnTypes.equals(that.columnTypes) : that.columnTypes != null) {
      return false;
    }
    return columnNames != null ? columnNames.equals(that.columnNames) : that.columnNames == null;
  }

  @Override
  public int hashCode()
  {
    int result = columnTypes != null ? columnTypes.hashCode() : 0;
    result = 31 * result + (columnNames != null ? columnNames.hashCode() : 0);
    return result;
  }

  @Override
  public String toString()
  {
    final StringBuilder s = new StringBuilder("RowSignature{");
    for (int i = 0; i < columnNames.size(); i++) {
      if (i > 0) {
        s.append(", ");
      }
      final String columnName = columnNames.get(i);
      s.append(columnName).append(":").append(getColumnType(columnName));
    }
    return s.append("}").toString();
  }

  public static class Builder
  {
    private final List> columnTypeList;

    private Builder()
    {
      this.columnTypeList = Lists.newArrayList();
    }

    public Builder add(String columnName, ValueType columnType)
    {
      columnTypeList.add(Pair.of(columnName, columnType));
      return this;
    }

    public RowSignature build()
    {
      return new RowSignature(columnTypeList);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy