github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/merge/keyless_integration_test.go (about) 1 // Copyright 2020 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package merge_test 16 17 import ( 18 "context" 19 "io" 20 "testing" 21 22 "github.com/stretchr/testify/assert" 23 "github.com/stretchr/testify/require" 24 25 cmd "github.com/dolthub/dolt/go/cmd/dolt/commands" 26 "github.com/dolthub/dolt/go/cmd/dolt/commands/cnfcmds" 27 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" 28 "github.com/dolthub/dolt/go/libraries/doltcore/doltdb/durable" 29 dtu "github.com/dolthub/dolt/go/libraries/doltcore/dtestutils" 30 "github.com/dolthub/dolt/go/libraries/doltcore/env" 31 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 32 "github.com/dolthub/dolt/go/store/hash" 33 "github.com/dolthub/dolt/go/store/pool" 34 "github.com/dolthub/dolt/go/store/prolly/tree" 35 "github.com/dolthub/dolt/go/store/types" 36 "github.com/dolthub/dolt/go/store/val" 37 ) 38 39 func TestKeylessMerge(t *testing.T) { 40 41 tests := []struct { 42 name string 43 setup []testCommand 44 expected keylessEntries 45 }{ 46 { 47 name: "fast-forward merge", 48 setup: []testCommand{ 49 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}}, 50 {cmd.AddCmd{}, []string{"."}}, 51 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 52 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 53 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}}, 54 {cmd.CommitCmd{}, []string{"-am", "added rows on other"}}, 55 {cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}}, 56 {cmd.MergeCmd{}, []string{"other"}}, 57 }, 58 expected: []keylessEntry{ 59 {2, 1, 2}, 60 {1, 3, 4}, 61 }, 62 }, 63 { 64 name: "3-way merge", 65 setup: []testCommand{ 66 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}}, 67 {cmd.AddCmd{}, []string{"."}}, 68 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 69 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 70 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}}, 71 {cmd.CommitCmd{}, []string{"-am", "added rows on other"}}, 72 {cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}}, 73 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (5,6);"}}, 74 {cmd.CommitCmd{}, []string{"-am", "added rows on main"}}, 75 {cmd.MergeCmd{}, []string{"other"}}, 76 }, 77 expected: []keylessEntry{ 78 {2, 1, 2}, 79 {1, 3, 4}, 80 {1, 5, 6}, 81 }, 82 }, 83 { 84 name: "3-way merge with duplicates", 85 setup: []testCommand{ 86 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}}, 87 {cmd.AddCmd{}, []string{"."}}, 88 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 89 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 90 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4), (3,4);"}}, 91 {cmd.CommitCmd{}, []string{"-am", "added rows on other"}}, 92 {cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}}, 93 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (5,6), (5,6);"}}, 94 {cmd.CommitCmd{}, []string{"-am", "added rows on main"}}, 95 {cmd.MergeCmd{}, []string{"other"}}, 96 }, 97 expected: []keylessEntry{ 98 {2, 1, 2}, 99 {2, 3, 4}, 100 {2, 5, 6}, 101 }, 102 }, 103 } 104 105 for _, test := range tests { 106 t.Run(test.name, func(t *testing.T) { 107 ctx := context.Background() 108 dEnv := dtu.CreateTestEnv() 109 defer dEnv.DoltDB.Close() 110 111 root, err := dEnv.WorkingRoot(ctx) 112 require.NoError(t, err) 113 root, err = doltdb.CreateEmptyTable(ctx, root, doltdb.TableName{Name: tblName}, keylessSch) 114 require.NoError(t, err) 115 err = dEnv.UpdateWorkingRoot(ctx, root) 116 require.NoError(t, err) 117 cliCtx, err := cmd.NewArgFreeCliContext(ctx, dEnv) 118 require.NoError(t, err) 119 120 for _, c := range test.setup { 121 exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cliCtx) 122 require.Equal(t, 0, exitCode) 123 } 124 125 root, err = dEnv.WorkingRoot(ctx) 126 require.NoError(t, err) 127 tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tblName}) 128 require.NoError(t, err) 129 130 assertKeylessRows(t, ctx, tbl, test.expected) 131 }) 132 } 133 } 134 135 func TestKeylessMergeConflicts(t *testing.T) { 136 tests := []struct { 137 name string 138 setup []testCommand 139 140 // Tuple( 141 // Tuple(baseVal) 142 // Tuple(val) 143 // Tuple(mergeVal) 144 // ) 145 conflicts conflictEntries 146 147 oursExpected keylessEntries 148 theirsExpected keylessEntries 149 }{ 150 { 151 name: "identical parallel changes", 152 setup: []testCommand{ 153 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2);"}}, 154 {cmd.AddCmd{}, []string{"."}}, 155 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 156 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 157 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}}, 158 {cmd.CommitCmd{}, []string{"-am", "added rows on other"}}, 159 {cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}}, 160 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (3,4);"}}, 161 {cmd.CommitCmd{}, []string{"-am", "added rows on main"}}, 162 {cmd.MergeCmd{}, []string{"other"}}, 163 }, 164 conflicts: []conflictEntry{ 165 { 166 base: nil, 167 ours: &keylessEntry{1, 3, 4}, 168 theirs: &keylessEntry{1, 3, 4}, 169 }, 170 }, 171 oursExpected: []keylessEntry{ 172 {2, 1, 2}, 173 {1, 3, 4}, 174 }, 175 theirsExpected: []keylessEntry{ 176 {2, 1, 2}, 177 {1, 3, 4}, 178 }, 179 }, 180 { 181 name: "asymmetric parallel deletes", 182 setup: []testCommand{ 183 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2),(1,2),(1,2);"}}, 184 {cmd.AddCmd{}, []string{"."}}, 185 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 186 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 187 {cmd.SqlCmd{}, []string{"-q", "delete from noKey where (c1,c2) = (1,2) limit 1;"}}, 188 {cmd.CommitCmd{}, []string{"-am", "deleted 1 row on other"}}, 189 {cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}}, 190 {cmd.SqlCmd{}, []string{"-q", "delete from noKey where (c1,c2) = (1,2) limit 2;"}}, 191 {cmd.CommitCmd{}, []string{"-am", "deleted 2 rows on main"}}, 192 {cmd.MergeCmd{}, []string{"other"}}, 193 }, 194 conflicts: []conflictEntry{ 195 { 196 base: &keylessEntry{4, 1, 2}, 197 ours: &keylessEntry{2, 1, 2}, 198 theirs: &keylessEntry{3, 1, 2}, 199 }, 200 }, 201 oursExpected: []keylessEntry{ 202 {2, 1, 2}, 203 }, 204 theirsExpected: []keylessEntry{ 205 {3, 1, 2}, 206 }, 207 }, 208 { 209 name: "asymmetric parallel updates", 210 setup: []testCommand{ 211 {cmd.SqlCmd{}, []string{"-q", "insert into noKey values (1,2),(1,2),(1,2),(1,2);"}}, 212 {cmd.AddCmd{}, []string{"."}}, 213 {cmd.CommitCmd{}, []string{"-am", "added rows"}}, 214 {cmd.CheckoutCmd{}, []string{"-b", "other"}}, 215 {cmd.SqlCmd{}, []string{"-q", "update noKey set c2 = 9 limit 1;"}}, 216 {cmd.CommitCmd{}, []string{"-am", "deleted 1 row on other"}}, 217 {cmd.CheckoutCmd{}, []string{env.DefaultInitBranch}}, 218 {cmd.SqlCmd{}, []string{"-q", "update noKey set c2 = 9 limit 2;"}}, 219 {cmd.CommitCmd{}, []string{"-am", "deleted 2 rows on main"}}, 220 {cmd.MergeCmd{}, []string{"other"}}, 221 }, 222 conflicts: []conflictEntry{ 223 { 224 base: &keylessEntry{4, 1, 2}, 225 ours: &keylessEntry{2, 1, 2}, 226 theirs: &keylessEntry{3, 1, 2}, 227 }, 228 { 229 base: nil, 230 ours: &keylessEntry{2, 1, 9}, 231 theirs: &keylessEntry{1, 1, 9}, 232 }, 233 }, 234 oursExpected: []keylessEntry{ 235 {2, 1, 2}, 236 {2, 1, 9}, 237 }, 238 theirsExpected: []keylessEntry{ 239 {3, 1, 2}, 240 {1, 1, 9}, 241 }, 242 }, 243 } 244 245 setupTest := func(t *testing.T, ctx context.Context, dEnv *env.DoltEnv, cc []testCommand) { 246 root, err := dEnv.WorkingRoot(ctx) 247 require.NoError(t, err) 248 root, err = doltdb.CreateEmptyTable(ctx, root, doltdb.TableName{Name: tblName}, keylessSch) 249 require.NoError(t, err) 250 err = dEnv.UpdateWorkingRoot(ctx, root) 251 require.NoError(t, err) 252 cliCtx, err := cmd.NewArgFreeCliContext(ctx, dEnv) 253 require.NoError(t, err) 254 255 for _, c := range cc { 256 exitCode := c.cmd.Exec(ctx, c.cmd.Name(), c.args, dEnv, cliCtx) 257 // allow merge to fail with conflicts 258 if _, ok := c.cmd.(cmd.MergeCmd); !ok { 259 require.Equal(t, 0, exitCode) 260 } 261 } 262 } 263 264 ctx := context.Background() 265 for _, test := range tests { 266 t.Run(test.name, func(t *testing.T) { 267 dEnv := dtu.CreateTestEnv() 268 defer dEnv.DoltDB.Close() 269 setupTest(t, ctx, dEnv, test.setup) 270 271 root, err := dEnv.WorkingRoot(ctx) 272 require.NoError(t, err) 273 tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tblName}) 274 require.NoError(t, err) 275 assertConflicts(t, ctx, tbl, test.conflicts) 276 }) 277 278 // conflict resolution 279 280 t.Run(test.name+"_resolved_ours", func(t *testing.T) { 281 dEnv := dtu.CreateTestEnv() 282 defer dEnv.DoltDB.Close() 283 284 setupTest(t, ctx, dEnv, test.setup) 285 cliCtx, verr := cmd.NewArgFreeCliContext(ctx, dEnv) 286 require.NoError(t, verr) 287 288 resolve := cnfcmds.ResolveCmd{} 289 args := []string{"--ours", tblName} 290 exitCode := resolve.Exec(ctx, resolve.Name(), args, dEnv, cliCtx) 291 require.Equal(t, 0, exitCode) 292 293 root, err := dEnv.WorkingRoot(ctx) 294 require.NoError(t, err) 295 tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tblName}) 296 require.NoError(t, err) 297 298 assertKeylessRows(t, ctx, tbl, test.oursExpected) 299 }) 300 t.Run(test.name+"_resolved_theirs", func(t *testing.T) { 301 dEnv := dtu.CreateTestEnv() 302 defer dEnv.DoltDB.Close() 303 304 setupTest(t, ctx, dEnv, test.setup) 305 cliCtx, verr := cmd.NewArgFreeCliContext(ctx, dEnv) 306 require.NoError(t, verr) 307 308 resolve := cnfcmds.ResolveCmd{} 309 args := []string{"--theirs", tblName} 310 exitCode := resolve.Exec(ctx, resolve.Name(), args, dEnv, cliCtx) 311 require.Equal(t, 0, exitCode) 312 313 root, err := dEnv.WorkingRoot(ctx) 314 require.NoError(t, err) 315 tbl, _, err := root.GetTable(ctx, doltdb.TableName{Name: tblName}) 316 require.NoError(t, err) 317 318 assertKeylessRows(t, ctx, tbl, test.theirsExpected) 319 }) 320 } 321 } 322 323 func assertConflicts(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected conflictEntries) { 324 if types.IsFormat_DOLT(tbl.Format()) { 325 assertProllyConflicts(t, ctx, tbl, expected) 326 return 327 } 328 assertNomsConflicts(t, ctx, tbl, expected) 329 } 330 331 func assertProllyConflicts(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected conflictEntries) { 332 artIdx, err := tbl.GetArtifacts(ctx) 333 require.NoError(t, err) 334 artM := durable.ProllyMapFromArtifactIndex(artIdx) 335 336 itr, err := artM.IterAllConflicts(ctx) 337 require.NoError(t, err) 338 339 expectedSet := expected.toConflictSet() 340 341 var c int 342 var h [16]byte 343 for { 344 conf, err := itr.Next(ctx) 345 if err == io.EOF { 346 break 347 } 348 require.NoError(t, err) 349 c++ 350 351 ours := mustGetRowValueFromTable(t, ctx, tbl, conf.Key) 352 theirs := mustGetRowValueFromRootIsh(t, ctx, tbl.ValueReadWriter(), tbl.NodeStore(), conf.TheirRootIsh, tblName, conf.Key) 353 base := mustGetRowValueFromRootIsh(t, ctx, tbl.ValueReadWriter(), tbl.NodeStore(), conf.Metadata.BaseRootIsh, tblName, conf.Key) 354 355 copy(h[:], conf.Key.GetField(0)) 356 expectedConf, ok := expectedSet[h] 357 require.True(t, ok) 358 359 if expectedConf.base != nil { 360 _, value := expectedConf.base.HashAndValue() 361 require.Equal(t, valDesc.Format(value), valDesc.Format(base)) 362 } 363 if expectedConf.ours != nil { 364 _, value := expectedConf.ours.HashAndValue() 365 require.Equal(t, valDesc.Format(value), valDesc.Format(ours)) 366 } 367 if expectedConf.theirs != nil { 368 _, value := expectedConf.theirs.HashAndValue() 369 require.Equal(t, valDesc.Format(value), valDesc.Format(theirs)) 370 } 371 } 372 373 require.Equal(t, len(expected), c) 374 375 } 376 377 func assertNomsConflicts(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected conflictEntries) { 378 _, confIdx, err := tbl.GetConflicts(ctx) 379 require.NoError(t, err) 380 conflicts := durable.NomsMapFromConflictIndex(confIdx) 381 382 assert.True(t, conflicts.Len() > 0) 383 assert.Equal(t, int(conflicts.Len()), len(expected)) 384 385 expectedSet := expected.toTupleSet() 386 387 actual, err := conflicts.Iterator(ctx) 388 require.NoError(t, err) 389 for { 390 _, act, err := actual.Next(ctx) 391 if act == nil { 392 return 393 } 394 assert.NoError(t, err) 395 h, err := act.Hash(types.Format_Default) 396 assert.NoError(t, err) 397 exp, ok := expectedSet[h] 398 assert.True(t, ok) 399 assert.True(t, exp.Equals(act)) 400 } 401 } 402 403 func mustGetRowValueFromTable(t *testing.T, ctx context.Context, tbl *doltdb.Table, key val.Tuple) val.Tuple { 404 idx, err := tbl.GetRowData(ctx) 405 require.NoError(t, err) 406 m := durable.ProllyMapFromIndex(idx) 407 408 var value val.Tuple 409 err = m.Get(ctx, key, func(_, v val.Tuple) error { 410 value = v 411 return nil 412 }) 413 require.NoError(t, err) 414 415 return value 416 } 417 418 func mustGetRowValueFromRootIsh(t *testing.T, ctx context.Context, vrw types.ValueReadWriter, ns tree.NodeStore, rootIsh hash.Hash, tblName string, key val.Tuple) val.Tuple { 419 rv, err := doltdb.LoadRootValueFromRootIshAddr(ctx, vrw, ns, rootIsh) 420 require.NoError(t, err) 421 tbl, ok, err := rv.GetTable(ctx, doltdb.TableName{Name: tblName}) 422 require.NoError(t, err) 423 require.True(t, ok) 424 425 return mustGetRowValueFromTable(t, ctx, tbl, key) 426 } 427 428 // |expected| is a tupleSet to compensate for random storage order 429 func assertKeylessRows(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected keylessEntries) { 430 if types.IsFormat_DOLT(tbl.Format()) { 431 assertKeylessProllyRows(t, ctx, tbl, expected) 432 return 433 } 434 435 assertKeylessNomsRows(t, ctx, tbl, expected) 436 } 437 438 func assertKeylessProllyRows(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected []keylessEntry) { 439 idx, err := tbl.GetRowData(ctx) 440 require.NoError(t, err) 441 m := durable.ProllyMapFromIndex(idx) 442 443 expectedSet := mustHash128Set(expected...) 444 445 itr, err := m.IterAll(ctx) 446 require.NoError(t, err) 447 448 var c int 449 var h [16]byte 450 for { 451 hashId, value, err := itr.Next(ctx) 452 if err == io.EOF { 453 break 454 } 455 c++ 456 require.NoError(t, err) 457 copy(h[:], hashId.GetField(0)) 458 expectedVal, ok := expectedSet[h] 459 assert.True(t, ok) 460 assert.Equal(t, valDesc.Format(expectedVal), valDesc.Format(value)) 461 } 462 463 require.Equal(t, len(expected), c) 464 } 465 466 func assertKeylessNomsRows(t *testing.T, ctx context.Context, tbl *doltdb.Table, expected keylessEntries) { 467 rowData, err := tbl.GetNomsRowData(ctx) 468 require.NoError(t, err) 469 470 assert.Equal(t, int(rowData.Len()), len(expected)) 471 472 expectedSet := expected.toTupleSet() 473 474 actual, err := rowData.Iterator(ctx) 475 require.NoError(t, err) 476 for { 477 _, act, err := actual.Next(ctx) 478 if act == nil { 479 break 480 } 481 assert.NoError(t, err) 482 h, err := act.Hash(types.Format_Default) 483 assert.NoError(t, err) 484 exp, ok := expectedSet[h] 485 assert.True(t, ok) 486 assert.True(t, exp.Equals(act)) 487 } 488 } 489 490 const tblName = "noKey" 491 492 var keylessSch = dtu.MustSchema( 493 schema.NewColumn("c1", 1, types.IntKind, false), 494 schema.NewColumn("c2", 2, types.IntKind, false), 495 ) 496 var c1Tag = types.Uint(1) 497 var c2Tag = types.Uint(2) 498 var cardTag = types.Uint(schema.KeylessRowCardinalityTag) 499 500 var valDesc = val.NewTupleDescriptor(val.Type{Enc: val.Uint64Enc}, val.Type{Enc: val.Int64Enc, Nullable: true}, val.Type{Enc: val.Int64Enc, Nullable: true}) 501 var valBld = val.NewTupleBuilder(valDesc) 502 var sharePool = pool.NewBuffPool() 503 504 type keylessEntries []keylessEntry 505 type keylessEntry struct { 506 card int 507 c1 int 508 c2 int 509 } 510 511 func (e keylessEntries) toTupleSet() tupleSet { 512 tups := make([]types.Tuple, len(e)) 513 for i, t := range e { 514 tups[i] = t.ToNomsTuple() 515 } 516 return mustTupleSet(tups...) 517 } 518 519 func (e keylessEntry) ToNomsTuple() types.Tuple { 520 return dtu.MustTuple(cardTag, types.Uint(e.card), c1Tag, types.Int(e.c1), c2Tag, types.Int(e.c2)) 521 } 522 523 func (e keylessEntry) HashAndValue() ([]byte, val.Tuple) { 524 valBld.PutUint64(0, uint64(e.card)) 525 valBld.PutInt64(1, int64(e.c1)) 526 valBld.PutInt64(2, int64(e.c2)) 527 528 value := valBld.Build(sharePool) 529 hashTup := val.HashTupleFromValue(sharePool, value) 530 return hashTup.GetField(0), value 531 } 532 533 type conflictSet map[[16]byte]conflictEntry 534 type conflictEntries []conflictEntry 535 type conflictEntry struct { 536 base, ours, theirs *keylessEntry 537 } 538 539 func (e conflictEntries) toConflictSet() conflictSet { 540 s := make(conflictSet, len(e)) 541 for _, t := range e { 542 s[t.Key()] = t 543 } 544 return s 545 } 546 547 func (e conflictEntries) toTupleSet() tupleSet { 548 tups := make([]types.Tuple, len(e)) 549 for i, t := range e { 550 tups[i] = t.ToNomsTuple() 551 } 552 return mustTupleSet(tups...) 553 } 554 555 func (e conflictEntry) Key() (h [16]byte) { 556 if e.base != nil { 557 h2, _ := e.base.HashAndValue() 558 copy(h[:], h2[:]) 559 return 560 } 561 if e.ours != nil { 562 h2, _ := e.ours.HashAndValue() 563 copy(h[:], h2[:]) 564 return 565 } 566 if e.theirs != nil { 567 h2, _ := e.theirs.HashAndValue() 568 copy(h[:], h2[:]) 569 return 570 } 571 572 return 573 } 574 575 func (e conflictEntry) ToNomsTuple() types.Tuple { 576 var b, o, t types.Value = types.NullValue, types.NullValue, types.NullValue 577 if e.base != nil { 578 b = e.base.ToNomsTuple() 579 } 580 if e.ours != nil { 581 o = e.ours.ToNomsTuple() 582 } 583 if e.theirs != nil { 584 t = e.theirs.ToNomsTuple() 585 } 586 return dtu.MustTuple(b, o, t) 587 } 588 589 type tupleSet map[hash.Hash]types.Tuple 590 591 func mustTupleSet(tt ...types.Tuple) (s tupleSet) { 592 s = make(tupleSet, len(tt)) 593 for _, tup := range tt { 594 h, err := tup.Hash(types.Format_Default) 595 if err != nil { 596 panic(err) 597 } 598 s[h] = tup 599 } 600 return 601 } 602 603 type hash128Set map[[16]byte]val.Tuple 604 605 func mustHash128Set(entries ...keylessEntry) (s hash128Set) { 606 var h [16]byte 607 s = make(hash128Set, len(entries)) 608 609 for _, e := range entries { 610 h2, value := e.HashAndValue() 611 copy(h[:], h2) 612 s[h] = value 613 } 614 615 return s 616 }