Skip to content

API, Core: Add toString to StructProjection and StructLikeUtil - #18342

Merged
amogh-jahagirdar merged 4 commits into
apache:mainfrom
anoopj:partition-struct-tostring
Oct 2, 2026
Merged

amogh-jahagirdar merged 4 commits into
apache:mainfrom
anoopj:partition-struct-tostring

Conversation

@anoopj

@anoopj anoopj commented Oct 1, 2026

Copy link
Copy Markdown
Member

StructProjection and the struct returned by StructLikeUtil.copy represent partition tuples but relied on the default Object.toString, which prints an unreadable identity hash when debugging. Render field names and values instead.

Followup from #18108

StructProjection and the struct returned by StructLikeUtil.copy represent
partition tuples but relied on the default Object.toString, which prints an
unreadable identity hash when debugging. Render field names and values
instead.

Followup from apache#18108

@amogh-jahagirdar amogh-jahagirdar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, I agree we need a more human readable name for these structs

@amogh-jahagirdar amogh-jahagirdar changed the title API, Core: Add toString to partition struct implementations API, Core: Add toString to StructProject and StructLikeUtil Oct 1, 2026
Comment on lines +229 to +239
StringBuilder sb = new StringBuilder();
sb.append("StructProjection{");
List<Types.NestedField> fields = type.fields();
for (int i = 0; i < fields.size(); i += 1) {
if (i > 0) {
sb.append(", ");
}
sb.append(fields.get(i).name()).append("=").append(get(i, Object.class));
}
sb.append("}");
return sb.toString();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we already used StringJoiner before in PartitionSet

StringJoiner result = new StringJoiner(", ", "[", "]");
, so it shall save some manual stringBuilder logic. Might worth considering something like below

Suggested change
StringBuilder sb = new StringBuilder();
sb.append("StructProjection{");
List<Types.NestedField> fields = type.fields();
for (int i = 0; i < fields.size(); i += 1) {
if (i > 0) {
sb.append(", ");
}
sb.append(fields.get(i).name()).append("=").append(get(i, Object.class));
}
sb.append("}");
return sb.toString();
List<Types.NestedField> fields = type.fields();
StringJoiner joiner = new StringJoiner(", ", "StructProjection{", "}");
for (int pos = 0; pos < fields.size(); pos += 1) {
joiner.add(fields.get(pos).name() + "=" + get(pos, Object.class));
}
return joiner.toString();

On a separate note, I noticed that if we use get(pos, Object.class) on deeply nested struct, it might have side effect of invoking javaClass.cast(nestedProjections[pos].wrap(nestedStruct)), so worth double checking whether we want the behavior as part of toString call.

@amogh-jahagirdar amogh-jahagirdar Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it might have side effect of invoking javaClass.cast(nestedProjections[pos].wrap(nestedStruct)),

This is a great callout, I missed this originally but any reason we can't just read from the actual struct? Otherwise logging one of these may lead to really unexpected results after the logging as a result of the side effect.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback. I changed it such that we are reading it from the original struct.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if helps to add a few tests in https://github.com/apache/iceberg/blob/main/api/src/test/java/org/apache/iceberg/util/TestStructProjection.java

Given the current form, the structProjection toString only show the top level field, which might not help much for the human-readable debug-ability. I do feel that the toString is effectively a nested materialization for struct projection, so probing is not free.

StructProjection.create(PROJECTED_STRUCT, PROJECTED_STRUCT).wrap(Row.of(1L, Row.of("John", "Q", "Doe"))).toString()  
//return  "StructProjection{id=1, person=org.apache.iceberg.TestHelpers$Row@447e510}"
//  instead of desired
// "StructProjection{id=1, person=StructProjection{first=John, middle=Q, last=Doe}}"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think both are good points. I initially hesitated to add tests because currently we don't have many toString tests in the project.

I made toString descend into nested projections. To avoid the wrap() side effect we discussed above, the nested descent uses copyFor, which wraps a fresh projection instead of mutating the shared nested one. This is OK, because we don't expect toString() to be used in the inner loop.

sb.append(values[i]);
}
sb.append("]");
return sb.toString();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is similar to StructTransform::toString with values is already object array, so can probably simplify to

Suggested change
return sb.toString();
return Arrays.stream(values)
.map(String::valueOf)
.collect(Collectors.joining(", ", "[", "]"));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks

@amogh-jahagirdar
amogh-jahagirdar self-requested a review October 1, 2026 19:16
@amogh-jahagirdar
amogh-jahagirdar dismissed their stale review October 1, 2026 19:17

missed a subtle subtle issue

@anoopj
anoopj requested a review from dramaticlly October 1, 2026 23:00

@dramaticlly dramaticlly left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @anoopj!

}

@Test
void toStringRendersNullNestedStructAsNull() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would be great to have coverage on StructProjection.createAllowMissing(DATA_STRUCT_MISSING_NESTED_FIELD, PROJECTED_STRUCT) as well.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added toStringRendersMissingNestedFieldAsNull

@amogh-jahagirdar amogh-jahagirdar changed the title API, Core: Add toString to StructProject and StructLikeUtil API, Core: Add toString to StructProjection and StructLikeUtil Oct 2, 2026

@amogh-jahagirdar amogh-jahagirdar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @anoopj and thank you @dramaticlly for the reviews!

@amogh-jahagirdar
amogh-jahagirdar merged commit 52ea8a4 into apache:main Oct 2, 2026
40 of 41 checks passed
@anoopj
anoopj deleted the partition-struct-tostring branch October 2, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants