blob: dfc15e0aecc1eb5f9e3e5b31e41536766f46c49d [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.vector.complex.fn;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.drill.common.expression.PathSegment;
import org.apache.drill.common.expression.SchemaPath;
import com.google.common.collect.Maps;
/**
* This class manages the projection pushdown for a complex path.
*/
public class FieldSelection {
public static final FieldSelection INVALID_NODE = new FieldSelection(null, ValidityMode.NEVER_VALID);
public static final FieldSelection ALL_VALID = new FieldSelection(null, ValidityMode.ALWAYS_VALID);
private enum ValidityMode {CHECK_CHILDREN, NEVER_VALID, ALWAYS_VALID}
private final Map<String, FieldSelection> children;
private final Map<String, FieldSelection> childrenInsensitive;
private ValidityMode mode;
private FieldSelection(){
this(new HashMap<String, FieldSelection>(), ValidityMode.CHECK_CHILDREN);
}
private FieldSelection(Map<String, FieldSelection> children, ValidityMode mode){
this.children = children;
if (children != null) {
childrenInsensitive = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
childrenInsensitive.putAll(children);
} else {
childrenInsensitive = null;
}
this.mode = mode;
}
@Override
public String toString() {
return
super.toString()
+ "[mode = " + mode
+ ", children = " + children
+ ", childrenInsensitive = " + childrenInsensitive + "]";
}
/**
* Create a new tree that has all leaves fixed to support full depth validity.
*/
private FieldSelection fixNodes(){
if(children.isEmpty()){
return ALL_VALID;
}else{
Map<String, FieldSelection> newMap = Maps.newHashMap();
for(Entry<String, FieldSelection> e : children.entrySet()){
newMap.put(e.getKey(), e.getValue().fixNodes());
}
return new FieldSelection(newMap, mode);
}
}
private FieldSelection addChild(String name){
name = name.toLowerCase();
if(children.containsKey(name)){
return children.get(name);
}
FieldSelection n = new FieldSelection();
children.put(name, n);
return n;
}
private void add(PathSegment segment){
if(segment.isNamed()){
boolean lastPath = segment.isLastPath();
FieldSelection child = addChild(segment.getNameSegment().getPath());
if (lastPath) {
child.setAlwaysValid();
}
if (!lastPath && !child.isAlwaysValid()) {
child.add(segment.getChild());
}
}
}
public boolean isNeverValid(){
return mode == ValidityMode.NEVER_VALID;
}
private void setAlwaysValid() {
mode = ValidityMode.ALWAYS_VALID;
}
public boolean isAlwaysValid() {
return mode == ValidityMode.ALWAYS_VALID;
}
public FieldSelection getChild(String name){
switch(mode){
case ALWAYS_VALID:
return ALL_VALID;
case CHECK_CHILDREN:
FieldSelection n = children.get(name);
// if we don't find, check to see if the lower case version of this path is available, if so, we'll add it with the new case to the original map.
if(n == null){
n = childrenInsensitive.get(name);
if(n != null){
children.put(name, n);
}
}
if(n == null){
return INVALID_NODE;
}else{
return n;
}
case NEVER_VALID:
return INVALID_NODE;
default:
throw new IllegalStateException();
}
}
private static boolean containsStar(List<SchemaPath> columns) {
for (SchemaPath expr : columns) {
if (SchemaPath.DYNAMIC_STAR.equals(expr.getRootSegment().getPath())) {
return true;
}
}
return false;
}
/**
* Generates a field selection based on a list of fields. Assumes that a partial path a.b is equivalent to a.b.*
* @param fields
* @return
*/
public static FieldSelection getFieldSelection(List<SchemaPath> fields){
if(containsStar(fields)){
return ALL_VALID;
}else{
FieldSelection root = new FieldSelection();
for(SchemaPath p : fields){
root.add(p.getRootSegment());
}
return root.fixNodes();
}
}
}