@@ -22,6 +22,7 @@ import (
2222 "errors"
2323 "fmt"
2424 "maps"
25+ "slices"
2526 "strings"
2627 "testing"
2728 "time"
@@ -160,6 +161,90 @@ func TestPositionDeletePartitionedFanoutWriterProcessBatch(t *testing.T) {
160161 }
161162}
162163
164+ func TestPositionDeletePartitionedFanoutWriterPartitionPathIsDeterministic (t * testing.T ) {
165+ t .Parallel ()
166+
167+ partitionSpec := iceberg .NewPartitionSpec (
168+ iceberg.PartitionField {
169+ FieldID : 1000 ,
170+ SourceID : 2147483546 , // file_path
171+ Name : "file_path" ,
172+ Transform : iceberg.IdentityTransform {},
173+ },
174+ iceberg.PartitionField {
175+ FieldID : 1001 ,
176+ SourceID : 2147483545 , // pos
177+ Name : "pos" ,
178+ Transform : iceberg.IdentityTransform {},
179+ },
180+ iceberg.PartitionField {
181+ FieldID : 1002 ,
182+ SourceID : 2147483545 , // pos
183+ Name : "pos_bucket" ,
184+ Transform : iceberg.BucketTransform {
185+ NumBuckets : 128 ,
186+ },
187+ },
188+ )
189+
190+ metadataBuilder , err := NewMetadataBuilder (2 )
191+ require .NoError (t , err )
192+ err = metadataBuilder .AddSchema (iceberg .PositionalDeleteSchema )
193+ require .NoError (t , err )
194+ err = metadataBuilder .SetCurrentSchemaID (0 )
195+ require .NoError (t , err )
196+ err = metadataBuilder .AddPartitionSpec (& partitionSpec , true )
197+ require .NoError (t , err )
198+ err = metadataBuilder .SetDefaultSpecID (0 )
199+ require .NoError (t , err )
200+ sortOrder , err := NewSortOrder (1 , []SortField {{
201+ SourceID : 2147483546 ,
202+ Direction : SortASC ,
203+ Transform : iceberg.IdentityTransform {},
204+ NullOrder : NullsFirst ,
205+ }})
206+ require .NoError (t , err )
207+ err = metadataBuilder .AddSortOrder (& sortOrder )
208+ require .NoError (t , err )
209+ err = metadataBuilder .SetDefaultSortOrderID (1 )
210+ require .NoError (t , err )
211+
212+ latestMeta , err := metadataBuilder .Build ()
213+ require .NoError (t , err )
214+
215+ writer := & positionDeletePartitionedFanoutWriter {
216+ metadata : latestMeta ,
217+ schema : iceberg .PositionalDeleteSchema ,
218+ }
219+
220+ ctx := partitionContext {
221+ specID : 0 ,
222+ partitionData : map [int ]any {
223+ 1000 : "file://ns/data-file.parquet" ,
224+ 1001 : int64 (42 ),
225+ 1002 : int32 (7 ),
226+ },
227+ }
228+
229+ expectedPath := partitionSpec .PartitionToPath (partitionRecord {
230+ ctx .partitionData [1000 ],
231+ ctx .partitionData [1001 ],
232+ ctx .partitionData [1002 ],
233+ }, iceberg .PositionalDeleteSchema )
234+
235+ // run multiple times to ensure it consistently
236+ // produces the same output for the same input context
237+ seen := make (map [string ]struct {})
238+ for range 1024 {
239+ path , err := writer .partitionPath (ctx )
240+ require .NoError (t , err )
241+ seen [path ] = struct {}{}
242+ }
243+
244+ require .Lenf (t , seen , 1 , "partition path must be stable for the same input map, got paths: %v" , slices .Collect (maps .Keys (seen )))
245+ require .Contains (t , seen , expectedPath )
246+ }
247+
163248func onlyContext (ctx context.Context , _ func ()) context.Context {
164249 return ctx
165250}
0 commit comments