-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Add support for struct field based filtering #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
a48e9d9
0c9c631
a5e4d16
dae1922
cdda798
f35e3d6
71aaba6
9f72f36
1097b11
8fa0a11
6438da5
deb5c59
f7de4e2
1deff5b
b1f59e5
cbb3a9f
25f6f91
b699257
f3f1783
a88603e
83227b0
2031eb5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,6 @@ | |
| package com.netflix.iceberg.expressions; | ||
|
|
||
| import com.netflix.iceberg.StructLike; | ||
| import com.netflix.iceberg.exceptions.ValidationException; | ||
| import com.netflix.iceberg.types.Type; | ||
| import com.netflix.iceberg.types.Types; | ||
| import java.util.List; | ||
|
|
@@ -32,19 +31,41 @@ public class BoundReference<T> implements Reference { | |
|
|
||
| BoundReference(Types.StructType struct, int fieldId) { | ||
| this.fieldId = fieldId; | ||
| this.pos = find(fieldId, struct); | ||
| this.type = struct.fields().get(pos).type(); | ||
| this.pos = findTopFieldPos(fieldId, struct); | ||
| this.type = (pos > -1) ? struct.fields().get(pos).type() : findStructFieldType(fieldId, struct); | ||
|
prodeezy marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| private int find(int fieldId, Types.StructType struct) { | ||
| private int findTopFieldPos(int fieldId, Types.StructType struct) { | ||
| List<Types.NestedField> fields = struct.fields(); | ||
| for (int i = 0; i < fields.size(); i += 1) { | ||
| if (fields.get(i).fieldId() == fieldId) { | ||
| return i; | ||
| } | ||
| } | ||
| throw new ValidationException( | ||
| "Cannot find top-level field id %d in struct: %s", fieldId, struct); | ||
|
|
||
| return -1; | ||
| } | ||
|
|
||
| private Type findStructFieldType(int fieldId, Types.StructType struct) { | ||
| List<Types.NestedField> fields = struct.fields(); | ||
| for (int i = 0; i < fields.size(); i += 1) { | ||
|
|
||
| if (fields.get(i).fieldId() == fieldId) { | ||
| return fields.get(i).type(); | ||
| } else { | ||
|
|
||
| // look under struct by calling this method recursively | ||
| if (fields.get(i).type() instanceof Types.StructType) { | ||
| Types.StructType subStruct = fields.get(i).type().asStructType(); | ||
|
|
||
| Type ret = findStructFieldType(fieldId, subStruct); | ||
| if (ret != null) { | ||
|
prodeezy marked this conversation as resolved.
Outdated
|
||
| return ret; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return null; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this method be returning a null value in case none of the nested fields of this struct match by field id?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done. |
||
| } | ||
|
|
||
| public Type type() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ | |
| package com.netflix.iceberg.expressions; | ||
|
|
||
| import com.netflix.iceberg.exceptions.ValidationException; | ||
| import com.netflix.iceberg.types.Type; | ||
| import com.netflix.iceberg.types.Types; | ||
|
|
||
| import static com.netflix.iceberg.expressions.Expression.Operation.IS_NULL; | ||
|
|
@@ -67,11 +68,13 @@ Expression bind(Types.StructType struct) { | |
| */ | ||
| public Expression bind(Types.StructType struct, boolean caseSensitive) { | ||
| Types.NestedField field; | ||
| if (caseSensitive) { | ||
| field = struct.field(ref().name()); | ||
| } else { | ||
| field = struct.caseInsensitiveField(ref().name()); | ||
| } | ||
| String expressionFieldPath = ref().name(); | ||
|
|
||
| boolean isStructFieldExp = expressionFieldPath.indexOf('.') > -1 && | ||
| struct.field(expressionFieldPath.split("\\.")[0]).type() instanceof Types.StructType; | ||
|
prodeezy marked this conversation as resolved.
Outdated
|
||
|
|
||
| field = isStructFieldExp ? findStructField(struct, expressionFieldPath, caseSensitive) : | ||
| caseSensitive ? struct.field(ref().name()) : struct.caseInsensitiveField(ref().name()); | ||
|
|
||
| ValidationException.check(field != null, | ||
| "Cannot find field '%s' in struct: %s", ref().name(), struct); | ||
|
|
@@ -132,4 +135,31 @@ public Expression bind(Types.StructType struct, boolean caseSensitive) { | |
| } | ||
| return new BoundPredicate<>(op(), new BoundReference<>(struct, field.fieldId()), lit); | ||
| } | ||
|
|
||
| private Types.NestedField findStructField(Types.StructType struct, String expressionFieldPath, | ||
| boolean caseSensitive) { | ||
|
|
||
| String[] structFields = expressionFieldPath.split("\\."); | ||
| String lastFieldInPath = structFields[structFields.length - 1]; | ||
|
|
||
| Types.StructType subField = struct; | ||
| int i=0; | ||
|
|
||
| Type nextFieldType = caseSensitive ? subField.field(structFields[i]).type() : | ||
| subField.caseInsensitiveField(structFields[i]).type(); | ||
|
|
||
| while(nextFieldType instanceof Types.StructType && i < structFields.length) { | ||
|
|
||
| subField = caseSensitive ? subField.field(structFields[i]).type().asStructType() : | ||
| subField.caseInsensitiveField(structFields[i]).type().asStructType(); | ||
|
|
||
| i++; | ||
|
|
||
| nextFieldType = caseSensitive ? subField.field(structFields[i]).type() : | ||
| subField.caseInsensitiveField(structFields[i]).type(); | ||
| } | ||
|
|
||
| return subField.field(lastFieldInPath); | ||
|
|
||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now that we need to recurse, I wonder if it makes sense to reuse the indexes available in Integer idx = indexByName.get(expressionFieldPath);
Types.NestedField field = indexById.get(idx);That code doesn't check for non-struct parents; we'd have to perhaps create a custom Indexer. Also we would need to calculate the index lazily and perhaps cache it ( As in the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't look like TypeUtils.indexByName & TypeUtils.indexById are used when reading data. It's used by the Manifest reader to index on manifest file statistics data.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess you are suggesting we now start using this. i dunno what impact that would have though. do you see a major benefit to it? I can look into it.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right.
Code reuse. But it would only make sense if
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. seems like it's being called many times but by different expression evaluators. A cache to lookup field name to Types.NestedField would help. Good call. will add one in.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @xabriel I'm now creating an index that keeps ids to accessors and then use accessors to reach type and fields. access to this index is lazy as well. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it mean to have a
pos = -1?I ask because although this field is
private, we do expose it viapos()andtoString()methods, so we may need to populate this field with the actual position of the matched inner struct.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good point. going to write some unit tests to evaluate impact. will handle accordingly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
handled this using accessors