blob: 056d3610bc9803253204b5a01a72aaa990191fc2 [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.project;
import java.util.List;
import org.apache.drill.common.exceptions.CustomErrorContext;
import org.apache.drill.common.expression.SchemaPath;
import org.apache.drill.exec.record.metadata.ColumnMetadata;
import org.apache.drill.exec.record.metadata.TupleNameSpace;
/**
* Represents an explicit projection at some tuple level. A tuple is the
* top-level row or a map.
* <p>
* A column is projected if it is explicitly listed in the selection list.
* <p>
* If a column is a map, then the projection for the map's columns is based on
* two rules:
* <ol>
* <li>If the projection list includes at least one explicit mention of a map
* member, then include only those columns explicitly listed.</li>
* <li>If the projection at the parent level lists only the map column itself
* (which the projection can't know is a map), then assume this implies all
* columns, as if the entry where "map.*".</li>
* </ol>
* <p>
* Examples:<br>
* <code>m</code><br>
* If <code>m</code> turns out to be a map, project all members of
* <code>m</code>.<br>
* <code>m.a</code><br>
* Column <code>m</code> must be a map. Project only column <code>a</code>.<br>
* <code>m, m.a</code><br>
* Tricky case. We interpret this as projecting only the "a" element of map m.
* <p>
* The projection set is built from a list of columns, represented as
* {@link SchemaPath} objects, provided by the physical plan. The structure of
* <tt>SchemaPath</tt> is a bit awkward:
* <p>
* <ul>
* <li><tt>SchemaPath> is a wrapper for a column which directly holds the
* <tt>NameSegment</tt> for the top-level column.</li>
* <li><tt>NameSegment</tt> holds a name. This can be a top name such as
* `a`, or parts of a compound name such as `a`.`b`. Each <tt>NameSegment</tt>
* has a "child" that points to the option following parts of the name.</li>
* <li><PathSegment</tt> is the base class for the parts of a name.</tt>
* <li><tt>ArraySegment</tt> is the other kind of name part and represents
* an array index such as the "[1]" in `columns`[1].</li>
* <ul>
* The parser considers names and array indexes. Example:<pre><code>
* a
* a.b
* a[2]
* a[2].b
* a[1][2][3]
* a[1][2][3].b.c
* a['foo'][0].b['bar']
* </code></pre>
*
* <h4>Usage</h4>
* The projection information is a <i>pattern</i> which supports queries of the
* form "is this column projected", and "if projected, is the projection consistent
* with such-and-so concrete type?" Clients should not try to work out the
* meaning of the pattern: doing so is very complex. Instead, do the following:
*
* <pre><code>
* String colName = ...;
* ColumnMetadata colDef = ...;
* InputTupleProjection tupleProj = ...
* if (tupleProj.isProjected(colName)) {
* if (!tupleProj.isComsistentWith(colDef)) {
* // Raise an error
* }
* // Handle a projected column.
* }</code></pre>
*/
public class RequestedTupleImpl implements RequestedTuple {
private final RequestedColumnImpl parent;
protected TupleProjectionType projectionType = TupleProjectionType.SOME;
private final TupleNameSpace<RequestedColumn> projection = new TupleNameSpace<>();
public RequestedTupleImpl() {
this.parent = null;
}
public RequestedTupleImpl(RequestedColumnImpl parent) {
this.parent = parent;
}
public RequestedTupleImpl(List<RequestedColumn> cols) {
parent = null;
for (RequestedColumn col : cols) {
projection.add(col.name(), col);
}
}
@Override
public int size() { return projection.count(); }
@Override
public RequestedColumn get(int i) {
return projection.get(i);
}
@Override
public RequestedColumn get(String colName) {
return projection.get(colName.toLowerCase());
}
protected RequestedColumnImpl getImpl(String colName) {
return (RequestedColumnImpl) get(colName);
}
protected RequestedColumn project(String colName) {
RequestedColumn col = get(colName);
if (col != null) {
if (col instanceof RequestedColumnImpl) {
((RequestedColumnImpl) col).bumpRefCount();
}
} else {
if (colName.equals(SchemaPath.DYNAMIC_STAR)) {
projectionType = TupleProjectionType.ALL;
col = new RequestedWildcardColumn(this, colName);
} else {
col = new RequestedColumnImpl(this, colName);
}
projection.add(colName, col);
}
return col;
}
@Override
public List<RequestedColumn> projections() {
return projection.entries();
}
@Override
public void buildName(StringBuilder buf) {
if (parent != null) {
parent.buildName(buf);
}
}
/**
* Tuple projection type. This is a rough approximation. A scan-level projection
* may include both a wildcard and implicit columns. This form is best used
* in testing where such ambiguities do not apply.
*/
@Override
public TupleProjectionType type() {
return projectionType;
}
@Override
public boolean isProjected(String colName) {
return projectionType == TupleProjectionType.ALL ? true : get(colName) != null;
}
@Override
public boolean isProjected(ColumnMetadata columnSchema) {
if (!isProjected(columnSchema.name())) {
return false;
}
return projectionType == TupleProjectionType.ALL ?
!Projections.excludeFromWildcard(columnSchema) : true;
}
@Override
public boolean enforceProjection(ColumnMetadata columnSchema, CustomErrorContext errorContext) {
if (projectionType == TupleProjectionType.ALL) {
return true;
}
RequestedColumn reqCol = get(columnSchema.name());
if (reqCol == null) {
return false;
}
ProjectionChecker.validateProjection(reqCol, columnSchema, errorContext);
return true;
}
@Override
public RequestedTuple mapProjection(String colName) {
switch (projectionType) {
case ALL:
return ImpliedTupleRequest.ALL_MEMBERS;
case NONE:
return ImpliedTupleRequest.NO_MEMBERS;
default:
RequestedColumnImpl colProj = getImpl(colName);
return colProj == null ? ImpliedTupleRequest.NO_MEMBERS : colProj.tuple();
}
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder()
.append("{");
boolean first = true;
for (RequestedColumn col : projections()) {
if (!first) {
buf.append(", ");
}
first = false;
buf.append(col.toString());
}
return buf.append("}").toString();
}
@Override
public boolean isEmpty() { return projections().isEmpty(); }
}