blob: f5af8949b3d361cd2da2d1730c302aa9d7330d90 [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.record.metadata;
import org.apache.drill.common.types.TypeProtos;
import org.apache.drill.common.types.Types;
import org.apache.drill.exec.vector.complex.DictVector;
/**
* Internal structure for building a dict. Dict is an array of key-value pairs
* with defined types for key and value (key and value fields are defined within {@link TupleSchema}).
* Key can be {@link org.apache.drill.common.types.TypeProtos.DataMode#REQUIRED} primitive,
* while value can be primitive or complex.
* <p>Column is added to the parent container during creation
* and all <tt>resumeXXX</tt> methods return qualified parent container.</p>
*
* @see DictVector
*/
public class DictBuilder implements SchemaContainer {
/**
* Schema containing key and value fields' definition.
*/
private final TupleSchema schema = new TupleSchema();
private final SchemaContainer parent;
private final String name;
private final TypeProtos.DataMode mode;
public DictBuilder(SchemaContainer parent, String name, TypeProtos.DataMode mode) {
this.parent = parent;
this.name = name;
this.mode = mode;
}
@Override
public void addColumn(ColumnMetadata column) {
// As dict does not support complex key, this method is used to
// nest a complex value only
if (!DictVector.FIELD_VALUE_NAME.equals(column.name())) {
String message = String.format(
"Expected column with name '%s'. Found: '%s'.", DictVector.FIELD_VALUE_NAME, column.name());
throw new IllegalArgumentException(message);
}
if (isFieldSet(column.name())) {
String message = String.format("Field '%s' is already defined in dict.", column.name());
throw new IllegalArgumentException(message);
}
schema.addColumn(column);
}
public DictBuilder key(TypeProtos.MinorType type) {
TypeProtos.MajorType keyType = Types.withMode(type, TypeProtos.DataMode.REQUIRED);
return key(keyType);
}
/**
* Use this method to set types with width or scale and precision,
* e.g. {@link org.apache.drill.common.types.TypeProtos.MinorType#VARDECIMAL} with scale and precision or
* {@link org.apache.drill.common.types.TypeProtos.MinorType#VARCHAR} etc.
*
* @param type desired type for key
* @return {@code this} builder
* @throws IllegalStateException if key field is already set
* @throws IllegalArgumentException if {@code type} is not supported (either complex or nullable)
*/
public DictBuilder key(TypeProtos.MajorType type) {
final String fieldName = DictVector.FIELD_KEY_NAME;
if (isFieldSet(fieldName)) {
throw new IllegalStateException(String.format("Filed '%s' is already defined.", fieldName));
}
if (!isSupportedKeyType(type)) {
throw new IllegalArgumentException(
String.format("'%s' in dict should be non-nullable primitive. Found: %s", fieldName, type));
}
addField(fieldName, type);
return this;
}
/**
* Checks if the field identified by name was already set.
*
* @param name name of the field
* @return {@code true} if the schema contains field with the {@code name}; {@code false} otherwise.
*/
private boolean isFieldSet(String name) {
return schema.index(name) != -1;
}
public DictBuilder value(TypeProtos.MinorType type) {
return value(type, TypeProtos.DataMode.REQUIRED);
}
public DictBuilder nullableValue(TypeProtos.MinorType type) {
return value(type, TypeProtos.DataMode.OPTIONAL);
}
public DictBuilder repeatedValue(TypeProtos.MinorType type) {
return value(type, TypeProtos.DataMode.REPEATED);
}
private DictBuilder value(TypeProtos.MinorType type, TypeProtos.DataMode mode) {
TypeProtos.MajorType valueType = Types.withMode(type, mode);
return value(valueType);
}
/**
* Define non-complex value type. For complex types use {@link #mapValue()}, {@link #mapArrayValue()} etc.
*
* @param type desired non-complex type for value.
* @return {@code this} builder
* @throws IllegalStateException if value is already set
* @throws IllegalArgumentException if {@code type} is either {@code MAP},
* {@code LIST}, {@code DICT} or {@code UNION}.
* @see #mapValue() method to define value as {@code MAP}
* @see #mapArrayValue() method to define value as {@code REPEATED MAP}
* @see #listValue() method to define value as {@code LIST}
* @see #unionValue() method to define value as {@code UNION}
* @see #dictValue() method to define value as {@code DICT}
* @see #dictArrayValue() method to define value as {@code REPEATED DICT}
*/
public DictBuilder value(TypeProtos.MajorType type) {
final String fieldName = DictVector.FIELD_VALUE_NAME;
if (isFieldSet(fieldName)) {
throw new IllegalStateException(String.format("Field '%s' is already defined.", fieldName));
}
if (Types.isComplex(type) || Types.isUnion(type)) {
String msg = String.format("Complex type found %s when defining '%s'. " +
"Use mapValue(), listValue() etc. in case of complex value type.", fieldName, type);
throw new IllegalArgumentException(msg);
}
addField(fieldName, type);
return this;
}
/**
* Adds field (either key or value) after validation to the schema.
*
* @param name name of the field
* @param type type of the field
*/
private void addField(String name, TypeProtos.MajorType type) {
ColumnBuilder builder = new ColumnBuilder(name, type.getMinorType())
.setMode(type.getMode());
if (type.hasScale()) {
builder.setPrecisionAndScale(type.getPrecision(), type.getScale());
} else if (type.hasPrecision()) {
builder.setPrecision(type.getPrecision());
}
if (type.hasWidth()) {
builder.setWidth(type.getWidth());
}
if (Types.isRepeated(type)) {
schema.add(SchemaBuilder.columnSchema(name, type.getMinorType(), type.getMode()));
return;
}
schema.add(builder.build());
}
public MapBuilder mapValue() {
return new MapBuilder(this, DictVector.FIELD_VALUE_NAME, TypeProtos.DataMode.REQUIRED);
}
public MapBuilder mapArrayValue() {
return new MapBuilder(this, DictVector.FIELD_VALUE_NAME, TypeProtos.DataMode.REPEATED);
}
public DictBuilder dictValue() {
return new DictBuilder(this, DictVector.FIELD_VALUE_NAME, TypeProtos.DataMode.REQUIRED);
}
public DictBuilder dictArrayValue() {
return new DictBuilder(this, DictVector.FIELD_VALUE_NAME, TypeProtos.DataMode.REPEATED);
}
public UnionBuilder unionValue() {
return new UnionBuilder(this, DictVector.FIELD_VALUE_NAME, TypeProtos.MinorType.UNION);
}
public UnionBuilder listValue() {
return new UnionBuilder(this, DictVector.FIELD_VALUE_NAME, TypeProtos.MinorType.LIST);
}
public RepeatedListBuilder repeatedListValue() {
return new RepeatedListBuilder(this, DictVector.FIELD_VALUE_NAME);
}
public DictColumnMetadata buildColumn() {
validateKeyValuePresent();
return new DictColumnMetadata(name, mode, schema);
}
private void validateKeyValuePresent() {
for (String fieldName : DictVector.fieldNames) {
ColumnMetadata columnMetadata = schema.metadata(fieldName);
if (columnMetadata == null) {
throw new IllegalStateException(String.format("Field %s is absent in DICT.", fieldName));
}
}
}
public void build() {
if (parent != null) {
parent.addColumn(buildColumn());
}
}
public SchemaBuilder resumeSchema() {
build();
return (SchemaBuilder) parent;
}
public MapBuilder resumeMap() {
build();
return (MapBuilder) parent;
}
public RepeatedListBuilder resumeList() {
build();
return (RepeatedListBuilder) parent;
}
public UnionBuilder resumeUnion() {
build();
return (UnionBuilder) parent;
}
public DictBuilder resumeDict() {
build();
return (DictBuilder) parent;
}
private boolean isSupportedKeyType(TypeProtos.MajorType type) {
return !Types.isComplex(type)
&& !Types.isUnion(type)
&& type.getMode() == TypeProtos.DataMode.REQUIRED;
}
}