Skip to content

fix: use transform equality for partition compatibility#1314

Merged
laskoviymishka merged 3 commits into
apache:mainfrom
fallintoplace:fix/partition-spec-transform-equals
Jun 29, 2026
Merged

fix: use transform equality for partition compatibility#1314
laskoviymishka merged 3 commits into
apache:mainfrom
fallintoplace:fix/partition-spec-transform-equals

Conversation

@fallintoplace

Copy link
Copy Markdown
Contributor

Summary

  • compare partition spec transforms with Transform.Equals in CompatibleWith
  • add regression coverage for non-comparable transform implementations

Why

PartitionField.Equals already delegates transform comparison to Transform.Equals, but PartitionSpec.CompatibleWith compared transform interfaces directly. That can panic for non-comparable transform implementations and also bypass transform-specific equality semantics.

Testing

  • go test . -run '^TestPartitionSpecCompatibleWithUsesTransformEquals$' -count=1\n- go test . -count=1\n- go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 run --timeout=10m .\n- git diff --check

@fallintoplace fallintoplace requested a review from zeroshade as a code owner June 24, 2026 21:36

@zeroshade zeroshade left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. Switching CompatibleWith from interface identity to Transform.Equals is correct, aligns with PartitionField.Equals, and removes the panic risk on non-comparable transforms. One optional coverage nit inline.

Comment thread partitions_test.go
assert.Equal(t, 1002, spec3.LastAssignedFieldID())
}

func TestPartitionSpecCompatibleWithUsesTransformEquals(t *testing.T) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: this nicely pins the non-comparable custom-transform path. It'd be worth also asserting the built-in parameterized cases directly at the CompatibleWith level - e.g. bucket[16] vs bucket[32] and truncate[4] vs truncate[8] should be incompatible, while identical transforms stay compatible. They work today via the built-in Equals impls, but a couple of table-driven cases here would guard against regressions.

@laskoviymishka laskoviymishka 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.

Nice catch — == on a Transform interface was a latent panic for any concrete type with a non-comparable field, and it bypassed transform-specific equality too. Swapping to .Equals() is the right call and matches what PartitionField.Equals and PartitionSpec.Equals already do; this really was the one spot that survived the old pattern.

The thing I'd settle before this merges is the regression test — as written it doesn't clearly prove the bug it guards against. The two require.NotPanics closures share one compatible bool, and NotPanics records a panic without FailNow, so on a panic the stale value still flows into the assertion below and you get a confusing double failure. Folding both calls into a single NotPanics closure makes the panic-safety check unambiguously the thing under test. A line on why nonComparableTransform is the fixture that matters — the []int field is what makes it non-comparable and what would have panicked under == — keeps it a durable regression test rather than something a future reader can't tell is load-bearing.

Tighten the test and I'm happy to take another pass and approve.

Comment thread partitions_test.go Outdated
Comment on lines +90 to +99
var compatible bool
require.NotPanics(t, func() {
compatible = spec.CompatibleWith(&sameTransformSpec)
})
assert.True(t, compatible)

require.NotPanics(t, func() {
compatible = spec.CompatibleWith(&differentTransformSpec)
})
assert.False(t, compatible)

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.

require.NotPanics records the panic but doesn't FailNow, so on a panic the stale compatible still flows into the assert.True/assert.False below and you get a confusing double failure. The two closures also share one var compatible bool, which reads like cross-closure capture and would race under t.Parallel().

I'd fold both into a single closure so the panic-safety check is unambiguously the thing under test:

require.NotPanics(t, func() {
	assert.True(t, spec.CompatibleWith(&sameTransformSpec))
	assert.False(t, spec.CompatibleWith(&differentTransformSpec))
})

While you're in here, a one-line comment on nonComparableTransform saying the []int field is what makes it non-comparable — and what would have panicked under the old == — would make it clear this fixture is load-bearing.

@laskoviymishka laskoviymishka 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.

:shipit:

@laskoviymishka laskoviymishka merged commit 54df1f0 into apache:main Jun 29, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants