parquet.thrift.struct.CompatibilityChecker Maven / Gradle / Ivy
/**
* Copyright 2012 Twitter, Inc.
*
* 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 parquet.thrift.struct;
import java.util.ArrayList;
import java.util.List;
/**
* A checker for thrift struct, returns compatibility report based on following rules:
* 1. Should not add new REQUIRED field in new thrift struct. Adding optional field is OK
* 2. Should not change field type for an existing field
* 3. Should not delete existing field
* 4. Should not make requirement type more restrictive for a field in new thrift struct
*
* @author Tianshuo Deng
*/
public class CompatibilityChecker {
public CompatibilityReport checkCompatibility(ThriftType.StructType oldStruct, ThriftType.StructType newStruct) {
CompatibleCheckerVisitor visitor = new CompatibleCheckerVisitor(oldStruct);
newStruct.accept(visitor);
return visitor.getReport();
}
}
class CompatibilityReport {
boolean isCompatible = true;
List messages = new ArrayList();
public boolean isCompatible() {
return isCompatible;
}
public void fail(String message) {
messages.add(message);
isCompatible = false;
}
public List getMessages() {
return messages;
}
}
class CompatibleCheckerVisitor implements ThriftType.TypeVisitor {
ThriftType oldType;
CompatibilityReport report = new CompatibilityReport();
CompatibleCheckerVisitor(ThriftType.StructType oldType) {
this.oldType = oldType;
}
public CompatibilityReport getReport() {
return report;
}
@Override
public void visit(ThriftType.MapType mapType) {
ThriftType.MapType currentOldType = ((ThriftType.MapType) oldType);
ThriftField oldKeyField = currentOldType.getKey();
ThriftField newKeyField = mapType.getKey();
ThriftField newValueField = mapType.getValue();
ThriftField oldValueField = currentOldType.getValue();
checkField(oldKeyField, newKeyField);
checkField(oldValueField, newValueField);
oldType = currentOldType;
}
@Override
public void visit(ThriftType.SetType setType) {
ThriftType.SetType currentOldType = ((ThriftType.SetType) oldType);
ThriftField oldField = currentOldType.getValues();
ThriftField newField = setType.getValues();
checkField(oldField, newField);
oldType = currentOldType;
}
@Override
public void visit(ThriftType.ListType listType) {
ThriftType.ListType currentOldType = ((ThriftType.ListType) oldType);
ThriftField oldField = currentOldType.getValues();
ThriftField newField = listType.getValues();
checkField(oldField, newField);
oldType = currentOldType;
}
public void fail(String message) {
report.fail(message);
}
private void checkField(ThriftField oldField, ThriftField newField) {
if (!newField.getType().getType().equals(oldField.getType().getType())) {
fail("type is not compatible: " + oldField.getName() + " " + oldField.getType().getType() + " vs " + newField.getType().getType());
return;
}
if (!newField.getName().equals(oldField.getName())) {
fail("field names are different: " + oldField.getName() + " vs " + newField.getName());
return;
}
if (firstIsMoreRestirctive(newField.getRequirement(), oldField.getRequirement())) {
fail("new field is more restrictive: " + newField.getName());
return;
}
oldType = oldField.getType();
newField.getType().accept(this);
}
private boolean firstIsMoreRestirctive(ThriftField.Requirement firstReq, ThriftField.Requirement secReq) {
if (firstReq == ThriftField.Requirement.REQUIRED && secReq != ThriftField.Requirement.REQUIRED) {
return true;
} else {
return false;
}
}
@Override
public void visit(ThriftType.StructType newStruct) {
ThriftType.StructType currentOldType = ((ThriftType.StructType) oldType);
short oldMaxId = 0;
for (ThriftField oldField : currentOldType.getChildren()) {
short fieldId = oldField.getFieldId();
if (fieldId > oldMaxId) {
oldMaxId = fieldId;
}
ThriftField newField = newStruct.getChildById(fieldId);
if (newField == null) {
fail("can not find index in new Struct: " + fieldId +" in " + newStruct);
return;
}
checkField(oldField, newField);
}
//check for new added
for (ThriftField newField : newStruct.getChildren()) {
//can not add required
if (newField.getRequirement() != ThriftField.Requirement.REQUIRED)
continue;//can add optional field
short newFieldId = newField.getFieldId();
if (newFieldId > oldMaxId) {
fail("new required field " + newField.getName() + " is added");
return;
}
if (newFieldId < oldMaxId && currentOldType.getChildById(newFieldId) == null) {
fail("new required field " + newField.getName() + " is added");
return;
}
}
//restore
oldType = currentOldType;
}
@Override
public void visit(ThriftType.EnumType enumType) {
return;
}
@Override
public void visit(ThriftType.BoolType boolType) {
return;
}
@Override
public void visit(ThriftType.ByteType byteType) {
return;
}
@Override
public void visit(ThriftType.DoubleType doubleType) {
return;
}
@Override
public void visit(ThriftType.I16Type i16Type) {
return;
}
@Override
public void visit(ThriftType.I32Type i32Type) {
return;
}
@Override
public void visit(ThriftType.I64Type i64Type) {
return;
}
@Override
public void visit(ThriftType.StringType stringType) {
return;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy