You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by bl...@apache.org on 2019/01/23 19:19:20 UTC
[incubator-iceberg] branch master updated: Add case sensitivity
flag to expression binding (#82)
This is an automated email from the ASF dual-hosted git repository.
blue pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-iceberg.git
The following commit(s) were added to refs/heads/master by this push:
new 022fe36 Add case sensitivity flag to expression binding (#82)
022fe36 is described below
commit 022fe36ba4afa7f22231df7a544eaae915a5153b
Author: Xabriel J. Collazo Mojica <xc...@adobe.com>
AuthorDate: Wed Jan 23 11:19:16 2019 -0800
Add case sensitivity flag to expression binding (#82)
This doesn't change default behavior. Configuring case sensitivity for processing engines will be added in future commits.
---
.../com/netflix/iceberg/expressions/Binder.java | 44 +++++++++++++++++++---
.../com/netflix/iceberg/expressions/Evaluator.java | 2 +-
.../expressions/InclusiveManifestEvaluator.java | 2 +-
.../expressions/InclusiveMetricsEvaluator.java | 2 +-
.../netflix/iceberg/expressions/Projections.java | 2 +-
.../iceberg/expressions/ResidualEvaluator.java | 4 +-
.../expressions/StrictMetricsEvaluator.java | 2 +-
.../iceberg/expressions/UnboundPredicate.java | 31 ++++++++++++++-
.../main/java/com/netflix/iceberg/types/Types.java | 17 ++++++++-
.../iceberg/expressions/TestExpressionBinding.java | 14 ++++++-
.../iceberg/expressions/TestPredicateBinding.java | 2 +-
.../netflix/iceberg/transforms/TestProjection.java | 4 +-
.../java/com/netflix/iceberg/BaseTableScan.java | 2 +-
.../parquet/ParquetDictionaryRowGroupFilter.java | 2 +-
.../netflix/iceberg/parquet/ParquetFilters.java | 4 +-
.../parquet/ParquetMetricsRowGroupFilter.java | 2 +-
.../netflix/iceberg/spark/SparkExpressions.java | 2 +-
.../com/netflix/iceberg/spark/SparkSchemaUtil.java | 4 +-
18 files changed, 114 insertions(+), 28 deletions(-)
diff --git a/api/src/main/java/com/netflix/iceberg/expressions/Binder.java b/api/src/main/java/com/netflix/iceberg/expressions/Binder.java
index e024a7c..b896e0c 100644
--- a/api/src/main/java/com/netflix/iceberg/expressions/Binder.java
+++ b/api/src/main/java/com/netflix/iceberg/expressions/Binder.java
@@ -51,31 +51,63 @@ public class Binder {
*
* @param struct The {@link StructType struct type} to resolve references by name.
* @param expr An {@link Expression expression} to rewrite with bound references.
+ * @param caseSensitive A boolean flag to control whether the bind should enforce case sensitivity.
* @return the expression rewritten with bound references
* @throws ValidationException if literals do not match bound references
* @throws IllegalStateException if any references are already bound
*/
public static Expression bind(StructType struct,
- Expression expr) {
- return ExpressionVisitors.visit(expr, new BindVisitor(struct));
+ Expression expr,
+ boolean caseSensitive) {
+ return ExpressionVisitors.visit(expr, new BindVisitor(struct, caseSensitive));
}
- public static Set<Integer> boundReferences(StructType struct, List<Expression> exprs) {
+ /**
+ * Replaces all unbound/named references with bound references to fields in the given struct,
+ * defaulting to case sensitive mode.
+ *
+ * Access modifier is package-private, to only allow use from existing tests.
+ *
+ * <p>
+ * When a reference is resolved, any literal used in a predicate for that field is converted to
+ * the field's type using {@link Literal#to(Type)}. If automatic conversion to that type isn't
+ * allowed, a {@link ValidationException validation exception} is thrown.
+ * <p>
+ * The result expression may be simplified when constructed. For example, {@code isNull("a")} is
+ * replaced with {@code alwaysFalse()} when {@code "a"} is resolved to a required field.
+ * <p>
+ * The expression cannot contain references that are already bound, or an
+ * {@link IllegalStateException} will be thrown.
+ *
+ * @param struct The {@link StructType struct type} to resolve references by name.
+ * @param expr An {@link Expression expression} to rewrite with bound references.
+ * @return the expression rewritten with bound references
+ *
+ * @throws IllegalStateException if any references are already bound
+ */
+ static Expression bind(StructType struct,
+ Expression expr) {
+ return Binder.bind(struct, expr, true);
+ }
+
+ public static Set<Integer> boundReferences(StructType struct, List<Expression> exprs, boolean caseSensitive) {
if (exprs == null) {
return ImmutableSet.of();
}
ReferenceVisitor visitor = new ReferenceVisitor();
for (Expression expr : exprs) {
- ExpressionVisitors.visit(bind(struct, expr), visitor);
+ ExpressionVisitors.visit(bind(struct, expr, caseSensitive), visitor);
}
return visitor.references;
}
private static class BindVisitor extends ExpressionVisitor<Expression> {
private final StructType struct;
+ private final boolean caseSensitive;
- private BindVisitor(StructType struct) {
+ private BindVisitor(StructType struct, boolean caseSensitive) {
this.struct = struct;
+ this.caseSensitive = caseSensitive;
}
@Override
@@ -110,7 +142,7 @@ public class Binder {
@Override
public <T> Expression predicate(UnboundPredicate<T> pred) {
- return pred.bind(struct);
+ return pred.bind(struct, caseSensitive);
}
}
diff --git a/api/src/main/java/com/netflix/iceberg/expressions/Evaluator.java b/api/src/main/java/com/netflix/iceberg/expressions/Evaluator.java
index 3854166..fc6eacd 100644
--- a/api/src/main/java/com/netflix/iceberg/expressions/Evaluator.java
+++ b/api/src/main/java/com/netflix/iceberg/expressions/Evaluator.java
@@ -44,7 +44,7 @@ public class Evaluator implements Serializable {
}
public Evaluator(Types.StructType struct, Expression unbound) {
- this.expr = Binder.bind(struct, unbound);
+ this.expr = Binder.bind(struct, unbound, true);
}
public boolean eval(StructLike data) {
diff --git a/api/src/main/java/com/netflix/iceberg/expressions/InclusiveManifestEvaluator.java b/api/src/main/java/com/netflix/iceberg/expressions/InclusiveManifestEvaluator.java
index cac617d..6493273 100644
--- a/api/src/main/java/com/netflix/iceberg/expressions/InclusiveManifestEvaluator.java
+++ b/api/src/main/java/com/netflix/iceberg/expressions/InclusiveManifestEvaluator.java
@@ -54,7 +54,7 @@ public class InclusiveManifestEvaluator {
public InclusiveManifestEvaluator(PartitionSpec spec, Expression rowFilter) {
this.struct = spec.partitionType();
- this.expr = Binder.bind(struct, rewriteNot(Projections.inclusive(spec).project(rowFilter)));
+ this.expr = Binder.bind(struct, rewriteNot(Projections.inclusive(spec).project(rowFilter)), true);
}
/**
diff --git a/api/src/main/java/com/netflix/iceberg/expressions/InclusiveMetricsEvaluator.java b/api/src/main/java/com/netflix/iceberg/expressions/InclusiveMetricsEvaluator.java
index 26c17e4..54cc0be 100644
--- a/api/src/main/java/com/netflix/iceberg/expressions/InclusiveMetricsEvaluator.java
+++ b/api/src/main/java/com/netflix/iceberg/expressions/InclusiveMetricsEvaluator.java
@@ -56,7 +56,7 @@ public class InclusiveMetricsEvaluator {
public InclusiveMetricsEvaluator(Schema schema, Expression unbound) {
this.schema = schema;
this.struct = schema.asStruct();
- this.expr = Binder.bind(struct, rewriteNot(unbound));
+ this.expr = Binder.bind(struct, rewriteNot(unbound), true);
}
/**
diff --git a/api/src/main/java/com/netflix/iceberg/expressions/Projections.java b/api/src/main/java/com/netflix/iceberg/expressions/Projections.java
index d9da053..b811e27 100644
--- a/api/src/main/java/com/netflix/iceberg/expressions/Projections.java
+++ b/api/src/main/java/com/netflix/iceberg/expressions/Projections.java
@@ -135,7 +135,7 @@ public class Projections {
@Override
public <T> Expression predicate(UnboundPredicate<T> pred) {
- Expression bound = pred.bind(spec.schema().asStruct());
+ Expression bound = pred.bind(spec.schema().asStruct(), true);
if (bound instanceof BoundPredicate) {
return predicate((BoundPredicate<?>) bound);
diff --git a/api/src/main/java/com/netflix/iceberg/expressions/ResidualEvaluator.java b/api/src/main/java/com/netflix/iceberg/expressions/ResidualEvaluator.java
index 290ae4a..610bdc5 100644
--- a/api/src/main/java/com/netflix/iceberg/expressions/ResidualEvaluator.java
+++ b/api/src/main/java/com/netflix/iceberg/expressions/ResidualEvaluator.java
@@ -170,7 +170,7 @@ public class ResidualEvaluator implements Serializable {
.projectStrict(part.name(), pred);
if (strictProjection != null) {
- Expression bound = strictProjection.bind(spec.partitionType());
+ Expression bound = strictProjection.bind(spec.partitionType(), true);
if (bound instanceof BoundPredicate) {
// the predicate methods will evaluate and return alwaysTrue or alwaysFalse
return super.predicate((BoundPredicate<?>) bound);
@@ -184,7 +184,7 @@ public class ResidualEvaluator implements Serializable {
@Override
public <T> Expression predicate(UnboundPredicate<T> pred) {
- Expression bound = pred.bind(spec.schema().asStruct());
+ Expression bound = pred.bind(spec.schema().asStruct(), true);
if (bound instanceof BoundPredicate) {
Expression boundResidual = predicate((BoundPredicate<?>) bound);
diff --git a/api/src/main/java/com/netflix/iceberg/expressions/StrictMetricsEvaluator.java b/api/src/main/java/com/netflix/iceberg/expressions/StrictMetricsEvaluator.java
index d3fa1df..702c255 100644
--- a/api/src/main/java/com/netflix/iceberg/expressions/StrictMetricsEvaluator.java
+++ b/api/src/main/java/com/netflix/iceberg/expressions/StrictMetricsEvaluator.java
@@ -57,7 +57,7 @@ public class StrictMetricsEvaluator {
public StrictMetricsEvaluator(Schema schema, Expression unbound) {
this.schema = schema;
this.struct = schema.asStruct();
- this.expr = Binder.bind(struct, rewriteNot(unbound));
+ this.expr = Binder.bind(struct, rewriteNot(unbound), true);
}
/**
diff --git a/api/src/main/java/com/netflix/iceberg/expressions/UnboundPredicate.java b/api/src/main/java/com/netflix/iceberg/expressions/UnboundPredicate.java
index 3523d1d..da6f981 100644
--- a/api/src/main/java/com/netflix/iceberg/expressions/UnboundPredicate.java
+++ b/api/src/main/java/com/netflix/iceberg/expressions/UnboundPredicate.java
@@ -44,8 +44,35 @@ public class UnboundPredicate<T> extends Predicate<T, NamedReference> {
return new UnboundPredicate<>(op().negate(), ref(), literal());
}
- public Expression bind(Types.StructType struct) {
- Types.NestedField field = struct.field(ref().name());
+ /**
+ * Bind this UnboundPredicate, defaulting to case sensitive mode.
+ *
+ * Access modifier is package-private, to only allow use from existing tests.
+ *
+ * @param struct The {@link Types.StructType struct type} to resolve references by name.
+ * @return an {@link Expression}
+ * @throws ValidationException if literals do not match bound references, or if comparison on expression is invalid
+ */
+ Expression bind(Types.StructType struct) {
+ return bind(struct, true);
+ }
+
+ /**
+ * Bind this UnboundPredicate.
+ *
+ * @param struct The {@link Types.StructType struct type} to resolve references by name.
+ * @param caseSensitive A boolean flag to control whether the bind should enforce case sensitivity.
+ * @return an {@link Expression}
+ * @throws ValidationException if literals do not match bound references, or if comparison on expression is invalid
+ */
+ public Expression bind(Types.StructType struct, boolean caseSensitive) {
+ Types.NestedField field;
+ if (caseSensitive) {
+ field = struct.field(ref().name());
+ } else {
+ field = struct.caseInsensitiveField(ref().name());
+ }
+
ValidationException.check(field != null,
"Cannot find field '%s' in struct: %s", ref().name(), struct);
diff --git a/api/src/main/java/com/netflix/iceberg/types/Types.java b/api/src/main/java/com/netflix/iceberg/types/Types.java
index 22111cf..a4ef5ac 100644
--- a/api/src/main/java/com/netflix/iceberg/types/Types.java
+++ b/api/src/main/java/com/netflix/iceberg/types/Types.java
@@ -55,7 +55,7 @@ public class Types {
private static final Pattern DECIMAL = Pattern.compile("decimal\\((\\d+),\\s+(\\d+)\\)");
public static PrimitiveType fromPrimitiveString(String typeString) {
- String lowerTypeString = typeString.toLowerCase(Locale.ENGLISH);
+ String lowerTypeString = typeString.toLowerCase(Locale.ROOT);
if (TYPES.containsKey(lowerTypeString)) {
return TYPES.get(lowerTypeString);
}
@@ -516,6 +516,7 @@ public class Types {
// lazy values
private transient List<NestedField> fieldList = null;
private transient Map<String, NestedField> fieldsByName = null;
+ private transient Map<String, NestedField> fieldsByLowerCaseName = null;
private transient Map<Integer, NestedField> fieldsById = null;
private StructType(List<NestedField> fields) {
@@ -535,6 +536,10 @@ public class Types {
return lazyFieldsByName().get(name);
}
+ public NestedField caseInsensitiveField(String name) {
+ return lazyFieldsByLowerCaseName().get(name.toLowerCase(Locale.ROOT));
+ }
+
@Override
public Type fieldType(String name) {
NestedField field = field(name);
@@ -600,6 +605,13 @@ public class Types {
return fieldsByName;
}
+ private Map<String, NestedField> lazyFieldsByLowerCaseName() {
+ if (fieldsByLowerCaseName == null) {
+ indexFields();
+ }
+ return fieldsByLowerCaseName;
+ }
+
private Map<Integer, NestedField> lazyFieldsById() {
if (fieldsById == null) {
indexFields();
@@ -609,12 +621,15 @@ public class Types {
private void indexFields() {
ImmutableMap.Builder<String, NestedField> byNameBuilder = ImmutableMap.builder();
+ ImmutableMap.Builder<String, NestedField> byLowerCaseNameBuilder = ImmutableMap.builder();
ImmutableMap.Builder<Integer, NestedField> byIdBuilder = ImmutableMap.builder();
for (NestedField field : fields) {
byNameBuilder.put(field.name(), field);
+ byLowerCaseNameBuilder.put(field.name().toLowerCase(Locale.ROOT), field);
byIdBuilder.put(field.fieldId(), field);
}
this.fieldsByName = byNameBuilder.build();
+ this.fieldsByLowerCaseName = byLowerCaseNameBuilder.build();
this.fieldsById = byIdBuilder.build();
}
}
diff --git a/api/src/test/java/com/netflix/iceberg/expressions/TestExpressionBinding.java b/api/src/test/java/com/netflix/iceberg/expressions/TestExpressionBinding.java
index 14b95b0..266e863 100644
--- a/api/src/test/java/com/netflix/iceberg/expressions/TestExpressionBinding.java
+++ b/api/src/test/java/com/netflix/iceberg/expressions/TestExpressionBinding.java
@@ -64,7 +64,19 @@ public class TestExpressionBinding {
@Test
public void testSingleReference() {
Expression expr = not(equal("x", 7));
- TestHelpers.assertAllReferencesBound("Single reference", Binder.bind(STRUCT, expr));
+ TestHelpers.assertAllReferencesBound("Single reference", Binder.bind(STRUCT, expr, true));
+ }
+
+ @Test
+ public void testCaseInsensitiveReference() {
+ Expression expr = not(equal("X", 7));
+ TestHelpers.assertAllReferencesBound("Single reference", Binder.bind(STRUCT, expr, false));
+ }
+
+ @Test(expected = ValidationException.class)
+ public void testCaseSensitiveReference() {
+ Expression expr = not(equal("X", 7));
+ Binder.bind(STRUCT, expr, true);
}
@Test
diff --git a/api/src/test/java/com/netflix/iceberg/expressions/TestPredicateBinding.java b/api/src/test/java/com/netflix/iceberg/expressions/TestPredicateBinding.java
index 433d20e..c30986f 100644
--- a/api/src/test/java/com/netflix/iceberg/expressions/TestPredicateBinding.java
+++ b/api/src/test/java/com/netflix/iceberg/expressions/TestPredicateBinding.java
@@ -179,7 +179,7 @@ public class TestPredicateBinding {
Assert.assertEquals("Less than or equal below min should be alwaysFalse",
Expressions.alwaysFalse(), lteqMin.bind(struct));
- Expression ltExpr = new UnboundPredicate<>(LT, ref("i"), (long) Integer.MAX_VALUE).bind(struct);
+ Expression ltExpr = new UnboundPredicate<>(LT, ref("i"), (long) Integer.MAX_VALUE).bind(struct, true);
BoundPredicate<Integer> ltMax = assertAndUnwrap(ltExpr);
Assert.assertEquals("Should translate bound to Integer",
(Integer) Integer.MAX_VALUE, ltMax.literal().value());
diff --git a/api/src/test/java/com/netflix/iceberg/transforms/TestProjection.java b/api/src/test/java/com/netflix/iceberg/transforms/TestProjection.java
index 8d8a34d..f08fe3f 100644
--- a/api/src/test/java/com/netflix/iceberg/transforms/TestProjection.java
+++ b/api/src/test/java/com/netflix/iceberg/transforms/TestProjection.java
@@ -71,7 +71,7 @@ public class TestProjection {
UnboundPredicate<?> projected = assertAndUnwrapUnbound(expr);
// check inclusive the bound predicate to ensure the types are correct
- BoundPredicate<?> bound = assertAndUnwrap(predicate.bind(spec.schema().asStruct()));
+ BoundPredicate<?> bound = assertAndUnwrap(predicate.bind(spec.schema().asStruct(), true));
Assert.assertEquals("Field name should match partition struct field",
"id", projected.ref().name());
@@ -109,7 +109,7 @@ public class TestProjection {
UnboundPredicate<?> projected = assertAndUnwrapUnbound(expr);
// check inclusive the bound predicate to ensure the types are correct
- BoundPredicate<?> bound = assertAndUnwrap(predicate.bind(spec.schema().asStruct()));
+ BoundPredicate<?> bound = assertAndUnwrap(predicate.bind(spec.schema().asStruct(), true));
Assert.assertEquals("Field name should match partition struct field",
"id", projected.ref().name());
diff --git a/core/src/main/java/com/netflix/iceberg/BaseTableScan.java b/core/src/main/java/com/netflix/iceberg/BaseTableScan.java
index bbfdc4f..0c44df7 100644
--- a/core/src/main/java/com/netflix/iceberg/BaseTableScan.java
+++ b/core/src/main/java/com/netflix/iceberg/BaseTableScan.java
@@ -129,7 +129,7 @@ class BaseTableScan implements TableScan {
// all of the filter columns are required
requiredFieldIds.addAll(
- Binder.boundReferences(table.schema().asStruct(), Collections.singletonList(rowFilter)));
+ Binder.boundReferences(table.schema().asStruct(), Collections.singletonList(rowFilter), true));
// all of the projection columns are required
requiredFieldIds.addAll(TypeUtil.getProjectedIds(table.schema().select(columns)));
diff --git a/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetDictionaryRowGroupFilter.java b/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetDictionaryRowGroupFilter.java
index f7db1fb..51d8c12 100644
--- a/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetDictionaryRowGroupFilter.java
+++ b/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetDictionaryRowGroupFilter.java
@@ -68,7 +68,7 @@ public class ParquetDictionaryRowGroupFilter {
public ParquetDictionaryRowGroupFilter(Schema schema, Expression unbound) {
this.schema = schema;
this.struct = schema.asStruct();
- this.expr = Binder.bind(struct, rewriteNot(unbound));
+ this.expr = Binder.bind(struct, rewriteNot(unbound), true);
}
/**
diff --git a/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetFilters.java b/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetFilters.java
index 11613aa..b93a8d8 100644
--- a/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetFilters.java
+++ b/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetFilters.java
@@ -161,7 +161,7 @@ class ParquetFilters {
}
protected Expression bind(UnboundPredicate<?> pred) {
- return pred.bind(schema.asStruct());
+ return pred.bind(schema.asStruct(), true);
}
@Override
@@ -189,7 +189,7 @@ class ParquetFilters {
protected Expression bind(UnboundPredicate<?> pred) {
// instead of binding the predicate using the top-level schema, bind it to the partition data
- return pred.bind(partitionStruct);
+ return pred.bind(partitionStruct, true);
}
}
diff --git a/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetMetricsRowGroupFilter.java b/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetMetricsRowGroupFilter.java
index 8694390..490eb16 100644
--- a/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetMetricsRowGroupFilter.java
+++ b/parquet/src/main/java/com/netflix/iceberg/parquet/ParquetMetricsRowGroupFilter.java
@@ -57,7 +57,7 @@ public class ParquetMetricsRowGroupFilter {
public ParquetMetricsRowGroupFilter(Schema schema, Expression unbound) {
this.schema = schema;
this.struct = schema.asStruct();
- this.expr = Binder.bind(struct, rewriteNot(unbound));
+ this.expr = Binder.bind(struct, rewriteNot(unbound), true);
}
/**
diff --git a/spark/src/main/java/com/netflix/iceberg/spark/SparkExpressions.java b/spark/src/main/java/com/netflix/iceberg/spark/SparkExpressions.java
index 99f0a81..25f38ff 100644
--- a/spark/src/main/java/com/netflix/iceberg/spark/SparkExpressions.java
+++ b/spark/src/main/java/com/netflix/iceberg/spark/SparkExpressions.java
@@ -336,7 +336,7 @@ public class SparkExpressions {
public static Expression convert(com.netflix.iceberg.expressions.Expression filter,
Schema schema) {
- return visit(Binder.bind(schema.asStruct(), filter), new ExpressionToSpark(schema));
+ return visit(Binder.bind(schema.asStruct(), filter, true), new ExpressionToSpark(schema));
}
private static class ExpressionToSpark extends ExpressionVisitors.
diff --git a/spark/src/main/java/com/netflix/iceberg/spark/SparkSchemaUtil.java b/spark/src/main/java/com/netflix/iceberg/spark/SparkSchemaUtil.java
index b63329f..bfe9390 100644
--- a/spark/src/main/java/com/netflix/iceberg/spark/SparkSchemaUtil.java
+++ b/spark/src/main/java/com/netflix/iceberg/spark/SparkSchemaUtil.java
@@ -202,7 +202,7 @@ public class SparkSchemaUtil {
* @throws IllegalArgumentException if the Spark type does not match the Schema
*/
public static Schema prune(Schema schema, StructType requestedType, List<Expression> filters) {
- Set<Integer> filterRefs = Binder.boundReferences(schema.asStruct(), filters);
+ Set<Integer> filterRefs = Binder.boundReferences(schema.asStruct(), filters, true);
return new Schema(visit(schema, new PruneColumnsWithoutReordering(requestedType, filterRefs))
.asNestedType()
.asStructType()
@@ -225,7 +225,7 @@ public class SparkSchemaUtil {
* @throws IllegalArgumentException if the Spark type does not match the Schema
*/
public static Schema prune(Schema schema, StructType requestedType, Expression filter) {
- Set<Integer> filterRefs = Binder.boundReferences(schema.asStruct(), Collections.singletonList(filter));
+ Set<Integer> filterRefs = Binder.boundReferences(schema.asStruct(), Collections.singletonList(filter), true);
return new Schema(visit(schema, new PruneColumnsWithoutReordering(requestedType, filterRefs))
.asNestedType()
.asStructType()