blob: 37f77a63136a9bf57d10de176b0bdb0d915c9ae4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.drill.exec.physical.resultSet.impl;
import org.apache.drill.common.exceptions.CustomErrorContext;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.exec.physical.resultSet.project.Projections;
import org.apache.drill.exec.physical.resultSet.project.RequestedTuple;
import org.apache.drill.exec.physical.resultSet.project.RequestedTuple.TupleProjectionType;
import org.apache.drill.exec.record.metadata.ColumnMetadata;
import org.apache.drill.exec.record.metadata.TupleMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Projection filter used when adding columns to the result set loader.
* Provides a variety of ways to filter columns: no filtering, filter
* by (parsed) projection list, or filter by projection list and
* provided schema. Enforces consistency of actual reader schema and
* projection list and/or provided schema.
* <p>
* Projection filters should not need to be extensible; filtering
* depends only on projection and provided schema.
*/
public interface ProjectionFilter {
Logger logger = LoggerFactory.getLogger(ProjectionFilter.class);
ProjectionFilter PROJECT_ALL = new ImplicitProjectionFilter(true);
ProjectionFilter PROJECT_NONE = new ImplicitProjectionFilter(false);
boolean isProjected(String colName);
boolean isProjected(ColumnMetadata columnSchema);
ProjectionFilter mapProjection(boolean isColProjected, String colName);
boolean isEmpty();
public static ProjectionFilter filterFor(RequestedTuple tupleProj,
CustomErrorContext errorContext) {
if (tupleProj.type() == TupleProjectionType.ALL) {
return PROJECT_ALL;
} else {
return new DirectProjectionFilter(tupleProj, errorContext);
}
}
public static ProjectionFilter filterFor(RequestedTuple tupleProj,
TupleMetadata providedSchema, CustomErrorContext errorContext) {
if (providedSchema == null) {
return filterFor(tupleProj, errorContext);
}
return new CompoundProjectionFilter(
new DirectProjectionFilter(tupleProj, errorContext),
new SchemaProjectionFilter(providedSchema, errorContext));
}
/**
* Implied projection: either project all or project none. Never
* projects special columns (those marked as not being expanded in
* SELECT *).
*/
public static class ImplicitProjectionFilter implements ProjectionFilter {
private final boolean projectAll;
public ImplicitProjectionFilter(boolean projectAll) {
this.projectAll = projectAll;
}
@Override
public boolean isProjected(String name) {
return projectAll;
}
@Override
public boolean isProjected(ColumnMetadata columnSchema) {
return projectAll ? !Projections.excludeFromWildcard(columnSchema) : false;
}
@Override
public ProjectionFilter mapProjection(boolean isColProjected, String colName) {
return isColProjected ? this : PROJECT_NONE;
}
@Override
public boolean isEmpty() {
return !projectAll;
}
}
/**
* Projection filter based on the (parsed) projection list. Enforces that
* the reader column is consistent with the form of projection (map,
* array, or plain) in the projection list.
*/
public static class DirectProjectionFilter implements ProjectionFilter {
private final RequestedTuple projectionSet;
private final CustomErrorContext errorContext;
public DirectProjectionFilter(RequestedTuple projectionSet, CustomErrorContext errorContext) {
this.projectionSet = projectionSet;
this.errorContext = errorContext;
}
@Override
public boolean isProjected(String colName) {
return projectionSet.isProjected(colName);
}
@Override
public boolean isProjected(ColumnMetadata columnSchema) {
return projectionSet.enforceProjection(columnSchema, errorContext);
}
@Override
public ProjectionFilter mapProjection(boolean isColProjected, String colName) {
return isColProjected ?
filterFor(projectionSet.mapProjection(colName), errorContext) :
PROJECT_NONE;
}
@Override
public boolean isEmpty() {
return projectionSet.isEmpty();
}
}
/**
* Projection based on a provided schema. If the schema is strict, a reader column
* is projected only if that column appears in the provided schema. Non-strict
* schema allow additional reader columns.
* <p>
* If the column is found, enforces that the reader schema has the same type and
* mode as the provided column.
*/
public static class SchemaProjectionFilter implements ProjectionFilter {
private final TupleMetadata providedSchema;
private final CustomErrorContext errorContext;
private final boolean isStrict;
public SchemaProjectionFilter(TupleMetadata providedSchema, CustomErrorContext errorContext) {
this(providedSchema,
providedSchema.booleanProperty(TupleMetadata.IS_STRICT_SCHEMA_PROP),
errorContext);
}
private SchemaProjectionFilter(TupleMetadata providedSchema, boolean isStrict, CustomErrorContext errorContext) {
this.providedSchema = providedSchema;
this.errorContext = errorContext;
this.isStrict = isStrict;
}
@Override
public boolean isProjected(String name) {
ColumnMetadata providedCol = providedSchema.metadata(name);
return providedCol != null || !isStrict;
}
@Override
public boolean isProjected(ColumnMetadata columnSchema) {
ColumnMetadata providedCol = providedSchema.metadata(columnSchema.name());
if (providedCol == null) {
return !isStrict;
}
if (providedCol.type() != columnSchema.type() ||
providedCol.mode() != columnSchema.mode()) {
throw UserException.validationError()
.message("Reader and provided column type mismatch")
.addContext("Provided column", providedCol.columnString())
.addContext("Reader column", columnSchema.columnString())
.addContext(errorContext)
.build(logger);
}
return true;
}
@Override
public ProjectionFilter mapProjection(boolean isColProjected, String colName) {
if (!isColProjected) {
return PROJECT_NONE;
}
ColumnMetadata providedCol = providedSchema.metadata(colName);
if (providedCol == null) {
return PROJECT_ALL;
}
if (!providedCol.isMap()) {
throw UserException.validationError()
.message("Reader expected a map column, but the the provided column is not a map")
.addContext("Provided column", providedCol.columnString())
.addContext("Reader column", colName)
.addContext(errorContext)
.build(logger);
}
return new SchemaProjectionFilter(providedCol.tupleSchema(), isStrict, errorContext);
}
@Override
public boolean isEmpty() {
return providedSchema.isEmpty();
}
}
/**
* Compound filter for combining direct and provided schema projections.
*/
public static class CompoundProjectionFilter implements ProjectionFilter {
private final ProjectionFilter filter1;
private final ProjectionFilter filter2;
public CompoundProjectionFilter(ProjectionFilter filter1, ProjectionFilter filter2) {
this.filter1 = filter1;
this.filter2 = filter2;
}
@Override
public boolean isProjected(String name) {
return filter1.isProjected(name) && filter2.isProjected(name);
}
@Override
public boolean isProjected(ColumnMetadata columnSchema) {
return filter1.isProjected(columnSchema) && filter2.isProjected(columnSchema);
}
@Override
public ProjectionFilter mapProjection(boolean isColProjected, String colName) {
ProjectionFilter childFilter1 = filter1.mapProjection(isColProjected, colName);
ProjectionFilter childFilter2 = filter2.mapProjection(isColProjected, colName);
if (childFilter1 == PROJECT_ALL) {
return childFilter2;
}
if (childFilter1 == PROJECT_NONE) {
return childFilter1;
}
if (childFilter2 == PROJECT_ALL) {
return childFilter1;
}
if (childFilter2 == PROJECT_NONE) {
return childFilter2;
}
return new CompoundProjectionFilter(childFilter1, childFilter2);
}
@Override
public boolean isEmpty() {
return filter1.isEmpty() && filter2.isEmpty();
}
}
}