/*
* 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.facebook.presto.sql.analyzer;
import com.facebook.presto.sql.tree.QualifiedName;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import javax.annotation.concurrent.Immutable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;
/**
* TODO: this needs to be merged with RowType at some point (when the type system is unified)
*/
@Immutable
public class RelationType
{
private final List<Field> visibleFields;
private final List<Field> allFields;
private final Map<Field, Integer> fieldIndexes;
public RelationType(Field... fields)
{
this(ImmutableList.copyOf(fields));
}
public RelationType(List<Field> fields)
{
requireNonNull(fields, "fields is null");
this.allFields = ImmutableList.copyOf(fields);
this.visibleFields = ImmutableList.copyOf(Iterables.filter(fields, not(Field::isHidden)));
int index = 0;
ImmutableMap.Builder<Field, Integer> builder = ImmutableMap.builder();
for (Field field : fields) {
builder.put(field, index++);
}
fieldIndexes = builder.build();
}
/**
* Gets the index of the specified field.
*
* @throws IllegalArgumentException when field is not found
*/
public int indexOf(Field field)
{
requireNonNull(field, "field cannot be null");
Integer index = fieldIndexes.get(field);
checkArgument(index != null, "Field %s not found", field);
return index;
}
/**
* Gets the field at the specified index.
*/
public Field getFieldByIndex(int fieldIndex)
{
checkElementIndex(fieldIndex, allFields.size(), "fieldIndex");
return allFields.get(fieldIndex);
}
/**
* Gets only the visible fields.
* No assumptions should be made about the order of the fields returned from this method.
* To obtain the index of a field, call indexOf.
*/
public Collection<Field> getVisibleFields()
{
return visibleFields;
}
public int getVisibleFieldCount()
{
return visibleFields.size();
}
/**
* Gets all fields including hidden fields.
* No assumptions should be made about the order of the fields returned from this method.
* To obtain the index of a field, call indexOf.
*/
public Collection<Field> getAllFields()
{
return ImmutableSet.copyOf(allFields);
}
/**
* Gets the count of all fields including hidden fields.
*/
public int getAllFieldCount()
{
return allFields.size();
}
/**
* This method is used for SELECT * or x.* queries
*/
public List<Field> resolveFieldsWithPrefix(Optional<QualifiedName> prefix)
{
return visibleFields.stream()
.filter(input -> input.matchesPrefix(prefix))
.collect(toImmutableList());
}
/**
* Gets the index of all columns matching the specified name
*/
public List<Field> resolveFields(QualifiedName name)
{
return allFields.stream()
.filter(input -> input.canResolve(name))
.collect(toImmutableList());
}
public boolean canResolve(QualifiedName name)
{
return !resolveFields(name).isEmpty();
}
/**
* Creates a new tuple descriptor containing all fields from this tuple descriptor
* and all fields from the specified tuple descriptor.
*/
public RelationType joinWith(RelationType other)
{
List<Field> fields = ImmutableList.<Field>builder()
.addAll(this.allFields)
.addAll(other.allFields)
.build();
return new RelationType(fields);
}
/**
* Creates a new tuple descriptor with the relation, and, optionally, the columns aliased.
*/
public RelationType withAlias(String relationAlias, List<String> columnAliases)
{
if (columnAliases != null) {
checkArgument(columnAliases.size() == visibleFields.size(),
"Column alias list has %s entries but '%s' has %s columns available",
columnAliases.size(),
relationAlias,
visibleFields.size());
}
ImmutableList.Builder<Field> fieldsBuilder = ImmutableList.builder();
for (int i = 0; i < allFields.size(); i++) {
Field field = allFields.get(i);
Optional<String> columnAlias = field.getName();
if (columnAliases == null) {
fieldsBuilder.add(Field.newQualified(
QualifiedName.of(relationAlias),
columnAlias,
field.getType(),
field.isHidden(),
field.getOriginTable(),
field.isAliased()));
}
else if (!field.isHidden()) {
// hidden fields are not exposed when there are column aliases
columnAlias = Optional.of(columnAliases.get(i));
fieldsBuilder.add(Field.newQualified(
QualifiedName.of(relationAlias),
columnAlias,
field.getType(),
false,
field.getOriginTable(),
field.isAliased()));
}
}
return new RelationType(fieldsBuilder.build());
}
/**
* Creates a new tuple descriptor containing only the visible fields.
*/
public RelationType withOnlyVisibleFields()
{
return new RelationType(visibleFields);
}
@Override
public String toString()
{
return allFields.toString();
}
}