You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by dz...@apache.org on 2022/07/20 17:17:46 UTC
[drill] branch 1.20 updated: DRILL-7960: Column metadata DECIMAL precision can exceed max supported value (#2597)
This is an automated email from the ASF dual-hosted git repository.
dzamo pushed a commit to branch 1.20
in repository https://gitbox.apache.org/repos/asf/drill.git
The following commit(s) were added to refs/heads/1.20 by this push:
new 2376ebcf90 DRILL-7960: Column metadata DECIMAL precision can exceed max supported value (#2597)
2376ebcf90 is described below
commit 2376ebcf9067e57e9f72ead1eeb39a4d7084bfd5
Author: James Turton <91...@users.noreply.github.com>
AuthorDate: Wed Jul 20 18:36:14 2022 +0200
DRILL-7960: Column metadata DECIMAL precision can exceed max supported value (#2597)
---
.../java/org/apache/drill/common/types/Types.java | 59 +++---
.../test/java/org/apache/drill/TestUnionAll.java | 203 +++++++++++++--------
.../exec/store/parquet/TestVarlenDecimal.java | 27 +++
.../drill/exec/record/metadata/MetadataUtils.java | 3 +
4 files changed, 190 insertions(+), 102 deletions(-)
diff --git a/common/src/main/java/org/apache/drill/common/types/Types.java b/common/src/main/java/org/apache/drill/common/types/Types.java
index b23fd6a654..775c248c64 100644
--- a/common/src/main/java/org/apache/drill/common/types/Types.java
+++ b/common/src/main/java/org/apache/drill/common/types/Types.java
@@ -32,6 +32,7 @@ import org.apache.drill.common.types.TypeProtos.MinorType;
import com.google.protobuf.TextFormat;
public class Types {
+ private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(Types.class);
public static final int MAX_VARCHAR_LENGTH = 65535;
public static final int UNDEFINED = 0;
@@ -806,32 +807,44 @@ public class Types {
* @return type builder
*/
public static MajorType.Builder calculateTypePrecisionAndScale(MajorType leftType, MajorType rightType, MajorType.Builder typeBuilder) {
- if (leftType.getMinorType().equals(rightType.getMinorType())) {
- boolean isScalarString = Types.isScalarStringType(leftType) && Types.isScalarStringType(rightType);
- boolean isDecimal = isDecimalType(leftType);
-
- if (isScalarString && leftType.hasPrecision() && rightType.hasPrecision()) {
- typeBuilder.setPrecision(Math.max(leftType.getPrecision(), rightType.getPrecision()));
+ if (!leftType.getMinorType().equals(rightType.getMinorType())) {
+ return typeBuilder;
+ }
+
+ if (Types.isScalarStringType(leftType) && leftType.hasPrecision() && rightType.hasPrecision()) {
+ return typeBuilder.setPrecision(Math.max(leftType.getPrecision(), rightType.getPrecision()));
+ }
+
+ MinorType minorType = leftType.getMinorType();
+ if (isDecimalType(leftType)) {
+ int scale = Math.max(leftType.getScale(), rightType.getScale());
+ // resulting precision should take into account resulting scale value and be calculated as
+ // sum of two components:
+ // - max integer digits number (precision - scale) for left and right;
+ // - resulting scale.
+ // So for the case of cast(9999 as decimal(4,0)) and cast(1.23 as decimal(3,2))
+ // resulting scale would be Max(0, 2) = 2 and resulting precision
+ // would be Max(4 - 0, 3 - 2) + 2 = 6.
+ // In this case, both values would fit into decimal(6, 2): 9999.00, 1.23
+ int leftNumberOfDigits = leftType.getPrecision() - leftType.getScale();
+ int rightNumberOfDigits = rightType.getPrecision() - rightType.getScale();
+ int precision = Math.max(leftNumberOfDigits, rightNumberOfDigits) + scale;
+ int maxPrecision = maxPrecision(minorType);
+
+ if (precision > maxPrecision) {
+ logger.warn(
+ "Possible loss of precision: wanted {}({}, {}) but limited to {}({}, {})",
+ minorType, precision, scale,
+ minorType, maxPrecision, scale
+ );
+ precision = maxPrecision;
}
- if (isDecimal) {
- int scale = Math.max(leftType.getScale(), rightType.getScale());
- // resulting precision should take into account resulting scale value and be calculated as
- // sum of two components:
- // - max integer digits number (precision - scale) for left and right;
- // - resulting scale.
- // So for the case of cast(9999 as decimal(4,0)) and cast(1.23 as decimal(3,2))
- // resulting scale would be Max(0, 2) = 2 and resulting precision
- // would be Max(4 - 0, 3 - 2) + 2 = 6.
- // In this case, both values would fit into decimal(6, 2): 9999.00, 1.23
- int leftNumberOfDigits = leftType.getPrecision() - leftType.getScale();
- int rightNumberOfDigits = rightType.getPrecision() - rightType.getScale();
- int precision = Math.max(leftNumberOfDigits, rightNumberOfDigits) + scale;
-
- typeBuilder.setPrecision(precision);
- typeBuilder.setScale(scale);
- }
+ typeBuilder.setPrecision(precision);
+ typeBuilder.setScale(scale);
+ return typeBuilder;
}
+
return typeBuilder;
}
diff --git a/exec/java-exec/src/test/java/org/apache/drill/TestUnionAll.java b/exec/java-exec/src/test/java/org/apache/drill/TestUnionAll.java
index 5a0047369d..5d0864ea25 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/TestUnionAll.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/TestUnionAll.java
@@ -27,11 +27,14 @@ import org.apache.drill.categories.UnlikelyTest;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.common.expression.SchemaPath;
import org.apache.drill.common.types.TypeProtos;
+import org.apache.drill.exec.ExecConstants;
+import org.apache.drill.exec.planner.physical.PlannerSettings;
import org.apache.drill.exec.record.BatchSchema;
import org.apache.drill.exec.record.metadata.SchemaBuilder;
import org.apache.drill.exec.work.foreman.SqlUnsupportedException;
import org.apache.drill.exec.work.foreman.UnsupportedRelOperatorException;
-import org.apache.drill.test.BaseTestQuery;
+import org.apache.drill.test.ClusterFixture;
+import org.apache.drill.test.ClusterTest;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -43,17 +46,14 @@ import java.nio.file.Paths;
import java.util.List;
@Category({SqlTest.class, OperatorTest.class})
-public class TestUnionAll extends BaseTestQuery {
-
- private static final String sliceTargetSmall = "alter session set `planner.slice_target` = 1";
- private static final String sliceTargetDefault = "alter session reset `planner.slice_target`";
- private static final String enableDistribute = "alter session set `planner.enable_unionall_distribute` = true";
- private static final String defaultDistribute = "alter session reset `planner.enable_unionall_distribute`";
+public class TestUnionAll extends ClusterTest {
+ private static final String SLICE_TARGET_DEFAULT = "alter session reset `planner.slice_target`";
private static final String EMPTY_DIR_NAME = "empty_directory";
@BeforeClass
- public static void setupTestFiles() {
+ public static void setupTestFiles() throws Exception {
+ startCluster(ClusterFixture.builder(dirTestWatcher));
dirTestWatcher.copyResourceToRoot(Paths.get("multilevel", "parquet"));
dirTestWatcher.makeTestTmpSubDir(Paths.get(EMPTY_DIR_NAME));
}
@@ -217,9 +217,9 @@ public class TestUnionAll extends BaseTestQuery {
@Category(UnlikelyTest.class)
public void testUnionAllViewExpandableStar() throws Exception {
try {
- test("use dfs.tmp");
- test("create view nation_view_testunionall_expandable_star as select n_name, n_nationkey from cp.`tpch/nation.parquet`;");
- test("create view region_view_testunionall_expandable_star as select r_name, r_regionkey from cp.`tpch/region.parquet`;");
+ run("use dfs.tmp");
+ run("create view nation_view_testunionall_expandable_star as select n_name, n_nationkey from cp.`tpch/nation.parquet`");
+ run("create view region_view_testunionall_expandable_star as select r_name, r_regionkey from cp.`tpch/region.parquet`");
String query1 = "(select * from dfs.tmp.`nation_view_testunionall_expandable_star`) " +
"union all " +
@@ -245,34 +245,34 @@ public class TestUnionAll extends BaseTestQuery {
.baselineColumns("r_name", "r_regionkey")
.build().run();
} finally {
- test("drop view if exists nation_view_testunionall_expandable_star");
- test("drop view if exists region_view_testunionall_expandable_star");
+ run("drop view if exists nation_view_testunionall_expandable_star");
+ run("drop view if exists region_view_testunionall_expandable_star");
}
}
@Test(expected = UnsupportedRelOperatorException.class) // see DRILL-2002
public void testUnionAllViewUnExpandableStar() throws Exception {
try {
- test("use dfs.tmp");
- test("create view nation_view_testunionall_expandable_star as select * from cp.`tpch/nation.parquet`;");
+ run("use dfs.tmp");
+ run("create view nation_view_testunionall_expandable_star as select * from cp.`tpch/nation.parquet`");
String query = "(select * from dfs.tmp.`nation_view_testunionall_expandable_star`) " +
"union all (select * from cp.`tpch/region.parquet`)";
- test(query);
+ run(query);
} catch(UserException ex) {
SqlUnsupportedException.errorClassNameToException(ex.getOrCreatePBError(false).getException().getExceptionClass());
throw ex;
} finally {
- test("drop view if exists nation_view_testunionall_expandable_star");
+ run("drop view if exists nation_view_testunionall_expandable_star");
}
}
@Test
public void testDiffDataTypesAndModes() throws Exception {
try {
- test("use dfs.tmp");
- test("create view nation_view_testunionall_expandable_star as select n_name, n_nationkey from cp.`tpch/nation.parquet`;");
- test("create view region_view_testunionall_expandable_star as select r_name, r_regionkey from cp.`tpch/region.parquet`;");
+ run("use dfs.tmp");
+ run("create view nation_view_testunionall_expandable_star as select n_name, n_nationkey from cp.`tpch/nation.parquet`");
+ run("create view region_view_testunionall_expandable_star as select r_name, r_regionkey from cp.`tpch/region.parquet`");
String t1 = "(select n_comment, n_regionkey from cp.`tpch/nation.parquet` limit 5)";
String t2 = "(select * from nation_view_testunionall_expandable_star limit 5)";
@@ -289,8 +289,8 @@ public class TestUnionAll extends BaseTestQuery {
.baselineColumns("n_comment", "n_regionkey")
.build().run();
} finally {
- test("drop view if exists nation_view_testunionall_expandable_star");
- test("drop view if exists region_view_testunionall_expandable_star");
+ run("drop view if exists nation_view_testunionall_expandable_star");
+ run("drop view if exists region_view_testunionall_expandable_star");
}
}
@@ -389,7 +389,7 @@ public class TestUnionAll extends BaseTestQuery {
String rootInt = "/store/json/intData.json";
String rootBoolean = "/store/json/booleanData.json";
- test("(select key from cp.`%s` " +
+ run("(select key from cp.`%s` " +
"union all " +
"select key from cp.`%s` )", rootInt, rootBoolean);
}
@@ -643,8 +643,11 @@ public class TestUnionAll extends BaseTestQuery {
".*SelectionVectorRemover.*\n" +
".*Filter.*\n" +
".*Scan.*columns=\\[`r_regionkey`\\].*"};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
// Validate the result
testBuilder()
@@ -685,8 +688,11 @@ public class TestUnionAll extends BaseTestQuery {
".*Filter.*\n" +
".*Scan.*columns=\\[`n_regionkey`, `n_nationkey`\\].*\n" +
".*Scan.*columns=\\[`r_regionkey`\\].*"};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
// Validate the result
testBuilder()
@@ -724,8 +730,11 @@ public class TestUnionAll extends BaseTestQuery {
".*Project.*\n" +
".*Scan.*columns=\\[`columns`\\[0\\]\\].*"};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
// Validate the result
testBuilder()
@@ -752,8 +761,12 @@ public class TestUnionAll extends BaseTestQuery {
".*Project.*\n" +
".*Scan.*columns=\\[`r_regionkey`, `r_name`\\].*"
};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
testBuilder()
.sqlQuery(query)
@@ -776,8 +789,12 @@ public class TestUnionAll extends BaseTestQuery {
".*UnionAll.*\n" +
".*Scan.*columns=\\[`n_nationkey`\\].*\n" +
".*Scan.*columns=\\[`r_regionkey`\\].*"};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
// Validate the result
testBuilder()
@@ -802,8 +819,12 @@ public class TestUnionAll extends BaseTestQuery {
".*Scan.*columns=\\[`n_nationkey`\\].*\n" +
".*Project\\(col=\\[\\*\\(2, \\$0\\)\\]\\).*\n" +
".*Scan.*columns=\\[`r_regionkey`\\].*"};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
// Validate the result
testBuilder()
@@ -830,8 +851,12 @@ public class TestUnionAll extends BaseTestQuery {
".*Scan.*columns=\\[`n_nationkey`\\].*\n" +
".*Project\\(col=\\[\\*\\(2, ITEM\\(\\$0, 0\\)\\)\\]\\).*\n" +
".*Scan.*columns=\\[`columns`\\[0\\]\\]"};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
// Validate the result
testBuilder()
@@ -856,8 +881,12 @@ public class TestUnionAll extends BaseTestQuery {
".*Scan.*columns=\\[`n_comment`, `n_nationkey`, `n_name`\\].*\n" +
".*Project.*\n" +
".*Scan.*columns=\\[`r_comment`, `r_regionkey`, `r_name`\\]"};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
// Validate the result
testBuilder()
@@ -887,8 +916,12 @@ public class TestUnionAll extends BaseTestQuery {
".*Filter.*\n" +
".*Scan.*columns=\\[`r_regionkey`\\]"
};
- final String[] excludedPlan = {};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
// Validate the result
testBuilder()
@@ -1028,22 +1061,24 @@ public class TestUnionAll extends BaseTestQuery {
// Validate the plan
final String[] expectedPlan = {"UnionExchange.*\n",
".*UnionAll"};
- final String[] excludedPlan = {};
try {
- test(sliceTargetSmall);
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+ client.alterSession(ExecConstants.SLICE_TARGET, 1);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
testBuilder()
- .optionSettingQueriesForTestQuery(sliceTargetSmall)
- .optionSettingQueriesForBaseline(sliceTargetDefault)
+ .optionSettingQueriesForBaseline(SLICE_TARGET_DEFAULT)
.unOrdered()
.sqlQuery(query)
.sqlBaselineQuery(query)
.build()
.run();
} finally {
- test(sliceTargetDefault);
+ client.resetSession(ExecConstants.SLICE_TARGET);
}
}
@@ -1059,22 +1094,24 @@ public class TestUnionAll extends BaseTestQuery {
// Validate the plan
final String[] expectedPlan = {"(?s)UnionExchange.*HashAgg.*HashToRandomExchange.*UnionAll.*"};
- final String[] excludedPlan = {};
try {
- test(sliceTargetSmall);
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+ client.alterSession(ExecConstants.SLICE_TARGET, 1);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
testBuilder()
- .optionSettingQueriesForTestQuery(sliceTargetSmall)
- .optionSettingQueriesForBaseline(sliceTargetDefault)
+ .optionSettingQueriesForBaseline(SLICE_TARGET_DEFAULT)
.unOrdered()
.sqlQuery(query)
.sqlBaselineQuery(query)
.build()
.run();
} finally {
- test(sliceTargetDefault);
+ client.resetSession(ExecConstants.SLICE_TARGET);
}
}
@@ -1089,22 +1126,24 @@ public class TestUnionAll extends BaseTestQuery {
// Validate the plan
final String[] expectedPlan = {"(?s)UnionExchange.*UnionAll.*HashJoin.*"};
- final String[] excludedPlan = {};
try {
- test(sliceTargetSmall);
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+ client.alterSession(ExecConstants.SLICE_TARGET, 1);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
testBuilder()
- .optionSettingQueriesForTestQuery(sliceTargetSmall)
- .optionSettingQueriesForBaseline(sliceTargetDefault)
+ .optionSettingQueriesForBaseline(SLICE_TARGET_DEFAULT)
.unOrdered()
.sqlQuery(query)
.sqlBaselineQuery(query)
.build()
.run();
} finally {
- test(sliceTargetDefault);
+ client.resetSession(ExecConstants.SLICE_TARGET);
}
}
@@ -1120,25 +1159,26 @@ public class TestUnionAll extends BaseTestQuery {
// Validate the plan
final String[] expectedPlan = {"(?s)UnionExchange.*UnionAll.*HashJoin.*"};
- final String[] excludedPlan = {};
try {
- test(sliceTargetSmall);
- test(enableDistribute);
-
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+ client.alterSession(ExecConstants.SLICE_TARGET, 1);
+ client.alterSession(PlannerSettings.UNIONALL_DISTRIBUTE_KEY, true);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
testBuilder()
- .optionSettingQueriesForTestQuery(sliceTargetSmall)
- .optionSettingQueriesForBaseline(sliceTargetDefault)
+ .optionSettingQueriesForBaseline(SLICE_TARGET_DEFAULT)
.unOrdered()
.sqlQuery(query)
.sqlBaselineQuery(query)
.build()
.run();
} finally {
- test(sliceTargetDefault);
- test(defaultDistribute);
+ client.resetSession(ExecConstants.SLICE_TARGET);
+ client.resetSession(PlannerSettings.UNIONALL_DISTRIBUTE_KEY);
}
}
@@ -1155,25 +1195,26 @@ public class TestUnionAll extends BaseTestQuery {
// Validate the plan
final String[] expectedPlan = {"(?s)UnionExchange.*UnionAll.*HashJoin.*"};
- final String[] excludedPlan = {};
try {
- test(sliceTargetSmall);
- test(enableDistribute);
-
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, excludedPlan);
+ client.alterSession(ExecConstants.SLICE_TARGET, 1);
+ client.alterSession(PlannerSettings.UNIONALL_DISTRIBUTE_KEY, true);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
testBuilder()
- .optionSettingQueriesForTestQuery(sliceTargetSmall)
- .optionSettingQueriesForBaseline(sliceTargetDefault)
+ .optionSettingQueriesForBaseline(SLICE_TARGET_DEFAULT)
.unOrdered()
.sqlQuery(query)
.sqlBaselineQuery(query)
.build()
.run();
} finally {
- test(sliceTargetDefault);
- test(defaultDistribute);
+ client.resetSession(ExecConstants.SLICE_TARGET);
+ client.resetSession(PlannerSettings.UNIONALL_DISTRIBUTE_KEY);
}
}
@@ -1311,7 +1352,12 @@ public class TestUnionAll extends BaseTestQuery {
".*Filter.*\n" +
".*Scan.*columns=\\[`r_regionkey`\\]"};
- PlanTestBase.testPlanMatchingPatterns(query, expectedPlan, null);
+ queryBuilder()
+ .sql(query)
+ .planMatcher()
+ .include(expectedPlan)
+ .match();
+
testBuilder()
.sqlQuery(query)
@@ -1341,5 +1387,4 @@ public class TestUnionAll extends BaseTestQuery {
.build()
.run();
}
-
}
diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/store/parquet/TestVarlenDecimal.java b/exec/java-exec/src/test/java/org/apache/drill/exec/store/parquet/TestVarlenDecimal.java
index 8cae6689ab..aad1c8815d 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/exec/store/parquet/TestVarlenDecimal.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/exec/store/parquet/TestVarlenDecimal.java
@@ -195,4 +195,31 @@ public class TestVarlenDecimal extends ClusterTest {
run("drop table if exists dfs.tmp.t");
}
}
+
+ @Test // DRILL-7960
+ public void testWideningLimit() throws Exception {
+ // A union of VARDECIMALs that requires a widening to an unsupported
+ // DECIMAL(40, 6). The resulting column should be limited DECIMAL(38, 6)
+ // and a precision loss warning logged.
+ String query = "SELECT CAST(10 AS DECIMAL(38, 4)) AS `Col1` " +
+ "UNION ALL " +
+ "SELECT CAST(22 AS DECIMAL(29, 6)) AS `Col1`";
+
+ testBuilder().sqlQuery(query)
+ .unOrdered()
+ .baselineColumns("Col1")
+ .baselineValues(new BigDecimal("10.000000"))
+ .baselineValues(new BigDecimal("22.000000"))
+ .go();
+
+ List<Pair<SchemaPath, TypeProtos.MajorType>> expectedSchema = Collections.singletonList(Pair.of(
+ SchemaPath.getSimplePath("Col1"),
+ Types.withPrecisionAndScale(MinorType.VARDECIMAL, DataMode.REQUIRED, 38, 6)
+ ));
+
+ testBuilder()
+ .sqlQuery(query)
+ .schemaBaseLine(expectedSchema)
+ .go();
+ }
}
diff --git a/exec/vector/src/main/java/org/apache/drill/exec/record/metadata/MetadataUtils.java b/exec/vector/src/main/java/org/apache/drill/exec/record/metadata/MetadataUtils.java
index e79412a2e1..3cd6d33d28 100644
--- a/exec/vector/src/main/java/org/apache/drill/exec/record/metadata/MetadataUtils.java
+++ b/exec/vector/src/main/java/org/apache/drill/exec/record/metadata/MetadataUtils.java
@@ -28,6 +28,9 @@ import org.apache.drill.common.types.Types;
import org.apache.drill.exec.record.MaterializedField;
import org.apache.drill.exec.vector.complex.DictVector;
+/**
+ * A collection of utility methods for working with column and tuple metadata.
+ */
public class MetadataUtils {
public static TupleSchema fromFields(Iterable<MaterializedField> fields) {