github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/store/datas/puller_test.go (about) 1 // Copyright 2019 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 datas 16 17 import ( 18 "context" 19 "encoding/json" 20 "errors" 21 "os" 22 "path/filepath" 23 "sync" 24 "testing" 25 26 "github.com/google/uuid" 27 "github.com/stretchr/testify/assert" 28 "github.com/stretchr/testify/require" 29 30 "github.com/dolthub/dolt/go/store/nbs" 31 "github.com/dolthub/dolt/go/store/types" 32 "github.com/dolthub/dolt/go/store/util/clienttest" 33 ) 34 35 func mustTuple(tpl types.Tuple, err error) types.Tuple { 36 if err != nil { 37 panic(err) 38 } 39 40 return tpl 41 } 42 43 func addTableValues(ctx context.Context, vrw types.ValueReadWriter, m types.Map, tableName string, alternatingKeyVals ...types.Value) (types.Map, error) { 44 val, ok, err := m.MaybeGet(ctx, types.String(tableName)) 45 46 if err != nil { 47 return types.EmptyMap, err 48 } 49 50 var tblMap types.Map 51 if ok { 52 mv, err := val.(types.Ref).TargetValue(ctx, vrw) 53 54 if err != nil { 55 return types.EmptyMap, err 56 } 57 58 me := mv.(types.Map).Edit() 59 60 for i := 0; i < len(alternatingKeyVals); i += 2 { 61 me.Set(alternatingKeyVals[i], alternatingKeyVals[i+1]) 62 } 63 64 tblMap, err = me.Map(ctx) 65 66 if err != nil { 67 return types.EmptyMap, err 68 } 69 } else { 70 tblMap, err = types.NewMap(ctx, vrw, alternatingKeyVals...) 71 72 if err != nil { 73 return types.EmptyMap, err 74 } 75 } 76 77 tblRef, err := writeValAndGetRef(ctx, vrw, tblMap) 78 79 if err != nil { 80 return types.EmptyMap, err 81 } 82 83 me := m.Edit() 84 me.Set(types.String(tableName), tblRef) 85 return me.Map(ctx) 86 } 87 88 func deleteTableValues(ctx context.Context, vrw types.ValueReadWriter, m types.Map, tableName string, keys ...types.Value) (types.Map, error) { 89 if len(keys) == 0 { 90 return m, nil 91 } 92 93 val, ok, err := m.MaybeGet(ctx, types.String(tableName)) 94 95 if err != nil { 96 return types.EmptyMap, err 97 } 98 99 if !ok { 100 return types.EmptyMap, errors.New("can't delete from table that wasn't created") 101 } 102 103 mv, err := val.(types.Ref).TargetValue(ctx, vrw) 104 105 if err != nil { 106 return types.EmptyMap, err 107 } 108 109 me := mv.(types.Map).Edit() 110 for _, k := range keys { 111 me.Remove(k) 112 } 113 114 tblMap, err := me.Map(ctx) 115 116 if err != nil { 117 return types.EmptyMap, err 118 } 119 120 tblRef, err := writeValAndGetRef(ctx, vrw, tblMap) 121 122 if err != nil { 123 return types.EmptyMap, err 124 } 125 126 me = m.Edit() 127 me.Set(types.String(tableName), tblRef) 128 return me.Map(ctx) 129 } 130 131 func tempDirDB(ctx context.Context) (Database, error) { 132 dir := filepath.Join(os.TempDir(), uuid.New().String()) 133 err := os.MkdirAll(dir, os.ModePerm) 134 135 if err != nil { 136 return nil, err 137 } 138 139 st, err := nbs.NewLocalStore(ctx, types.Format_Default.VersionString(), dir, clienttest.DefaultMemTableSize) 140 141 if err != nil { 142 return nil, err 143 } 144 145 return NewDatabase(st), nil 146 } 147 148 func TestPuller(t *testing.T) { 149 deltas := []struct { 150 name string 151 sets map[string][]types.Value 152 deletes map[string][]types.Value 153 tblDeletes []string 154 }{ 155 { 156 "empty", 157 map[string][]types.Value{}, 158 map[string][]types.Value{}, 159 []string{}, 160 }, 161 { 162 "employees", 163 map[string][]types.Value{ 164 "employees": { 165 mustTuple(types.NewTuple(types.Format_Default, types.String("Hendriks"), types.String("Brian"))), 166 mustTuple(types.NewTuple(types.Format_Default, types.String("Software Engineer"), types.Int(39))), 167 mustTuple(types.NewTuple(types.Format_Default, types.String("Sehn"), types.String("Timothy"))), 168 mustTuple(types.NewTuple(types.Format_Default, types.String("CEO"), types.Int(39))), 169 mustTuple(types.NewTuple(types.Format_Default, types.String("Son"), types.String("Aaron"))), 170 mustTuple(types.NewTuple(types.Format_Default, types.String("Software Engineer"), types.Int(36))), 171 }, 172 }, 173 map[string][]types.Value{}, 174 []string{}, 175 }, 176 { 177 "ip to country", 178 map[string][]types.Value{ 179 "ip_to_country": { 180 types.String("5.183.230.1"), types.String("BZ"), 181 types.String("5.180.188.1"), types.String("AU"), 182 types.String("2.56.9.244"), types.String("GB"), 183 types.String("20.175.7.56"), types.String("US"), 184 }, 185 }, 186 map[string][]types.Value{}, 187 []string{}, 188 }, 189 { 190 "more ips", 191 map[string][]types.Value{ 192 "ip_to_country": { 193 types.String("20.175.193.85"), types.String("US"), 194 types.String("5.196.110.191"), types.String("FR"), 195 types.String("4.14.242.160"), types.String("CA"), 196 }, 197 }, 198 map[string][]types.Value{}, 199 []string{}, 200 }, 201 { 202 "more employees", 203 map[string][]types.Value{ 204 "employees": { 205 mustTuple(types.NewTuple(types.Format_Default, types.String("Jesuele"), types.String("Matt"))), 206 mustTuple(types.NewTuple(types.Format_Default, types.String("Software Engineer"), types.NullValue)), 207 mustTuple(types.NewTuple(types.Format_Default, types.String("Wilkins"), types.String("Daylon"))), 208 mustTuple(types.NewTuple(types.Format_Default, types.String("Software Engineer"), types.NullValue)), 209 mustTuple(types.NewTuple(types.Format_Default, types.String("Katie"), types.String("McCulloch"))), 210 mustTuple(types.NewTuple(types.Format_Default, types.String("Software Engineer"), types.NullValue)), 211 }, 212 }, 213 map[string][]types.Value{}, 214 []string{}, 215 }, 216 { 217 "delete ips table", 218 map[string][]types.Value{}, 219 map[string][]types.Value{}, 220 []string{"ip_to_country"}, 221 }, 222 { 223 "delete some employees", 224 map[string][]types.Value{}, 225 map[string][]types.Value{ 226 "employees": { 227 mustTuple(types.NewTuple(types.Format_Default, types.String("Hendriks"), types.String("Brian"))), 228 mustTuple(types.NewTuple(types.Format_Default, types.String("Sehn"), types.String("Timothy"))), 229 mustTuple(types.NewTuple(types.Format_Default, types.String("Son"), types.String("Aaron"))), 230 }, 231 }, 232 []string{}, 233 }, 234 } 235 236 ctx := context.Background() 237 db, err := tempDirDB(ctx) 238 require.NoError(t, err) 239 ds, err := db.GetDataset(ctx, "ds") 240 require.NoError(t, err) 241 rootMap, err := types.NewMap(ctx, db) 242 require.NoError(t, err) 243 244 parent, err := types.NewList(ctx, db) 245 require.NoError(t, err) 246 states := map[string]types.Ref{} 247 for _, delta := range deltas { 248 for tbl, sets := range delta.sets { 249 rootMap, err = addTableValues(ctx, db, rootMap, tbl, sets...) 250 require.NoError(t, err) 251 } 252 253 for tbl, dels := range delta.deletes { 254 rootMap, err = deleteTableValues(ctx, db, rootMap, tbl, dels...) 255 require.NoError(t, err) 256 } 257 258 me := rootMap.Edit() 259 for _, tbl := range delta.tblDeletes { 260 me.Remove(types.String(tbl)) 261 } 262 rootMap, err = me.Map(ctx) 263 require.NoError(t, err) 264 265 commitOpts := CommitOptions{ParentsList: parent} 266 ds, err = db.Commit(ctx, ds, rootMap, commitOpts) 267 require.NoError(t, err) 268 269 r, ok, err := ds.MaybeHeadRef() 270 require.NoError(t, err) 271 require.True(t, ok) 272 273 parent, err = types.NewList(ctx, db, r) 274 require.NoError(t, err) 275 276 states[delta.name] = r 277 } 278 279 tbl, err := makeABigTable(ctx, db) 280 require.NoError(t, err) 281 282 tblRef, err := writeValAndGetRef(ctx, db, tbl) 283 require.NoError(t, err) 284 285 me := rootMap.Edit() 286 me.Set(types.String("big_table"), tblRef) 287 rootMap, err = me.Map(ctx) 288 require.NoError(t, err) 289 290 commitOpts := CommitOptions{ParentsList: parent} 291 ds, err = db.Commit(ctx, ds, rootMap, commitOpts) 292 require.NoError(t, err) 293 294 r, ok, err := ds.MaybeHeadRef() 295 require.NoError(t, err) 296 require.True(t, ok) 297 298 states["add big table"] = r 299 300 for k, rootRef := range states { 301 t.Run(k, func(t *testing.T) { 302 eventCh := make(chan PullerEvent, 128) 303 wg := new(sync.WaitGroup) 304 wg.Add(1) 305 go func() { 306 defer wg.Done() 307 for evt := range eventCh { 308 var details interface{} 309 switch evt.EventType { 310 case NewLevelTWEvent, DestDBHasTWEvent, LevelUpdateTWEvent: 311 details = evt.TWEventDetails 312 default: 313 details = evt.TFEventDetails 314 } 315 316 jsonBytes, err := json.Marshal(details) 317 318 if err == nil { 319 t.Logf("event_type: %d details: %s\n", evt.EventType, string(jsonBytes)) 320 } 321 } 322 }() 323 324 sinkdb, err := tempDirDB(ctx) 325 require.NoError(t, err) 326 327 tmpDir := filepath.Join(os.TempDir(), uuid.New().String()) 328 err = os.MkdirAll(tmpDir, os.ModePerm) 329 require.NoError(t, err) 330 plr, err := NewPuller(ctx, tmpDir, 128, db, sinkdb, rootRef.TargetHash(), eventCh) 331 require.NoError(t, err) 332 333 err = plr.Pull(ctx) 334 close(eventCh) 335 require.NoError(t, err) 336 wg.Wait() 337 338 sinkDS, err := sinkdb.GetDataset(ctx, "ds") 339 require.NoError(t, err) 340 sinkDS, err = sinkdb.FastForward(ctx, sinkDS, rootRef) 341 require.NoError(t, err) 342 343 require.NoError(t, err) 344 sinkRootRef, ok, err := sinkDS.MaybeHeadRef() 345 require.NoError(t, err) 346 require.True(t, ok) 347 348 eq, err := pullerRefEquality(ctx, rootRef, sinkRootRef, db, sinkdb) 349 require.NoError(t, err) 350 assert.True(t, eq) 351 352 }) 353 } 354 } 355 356 func makeABigTable(ctx context.Context, db Database) (types.Map, error) { 357 m, err := types.NewMap(ctx, db) 358 359 if err != nil { 360 return types.EmptyMap, nil 361 } 362 363 me := m.Edit() 364 365 for i := 0; i < 256*1024; i++ { 366 tpl, err := types.NewTuple(db.Format(), types.UUID(uuid.New()), types.String(uuid.New().String()), types.Float(float64(i))) 367 368 if err != nil { 369 return types.EmptyMap, err 370 } 371 372 me.Set(types.Int(i), tpl) 373 } 374 375 return me.Map(ctx) 376 } 377 378 func pullerRefEquality(ctx context.Context, expectad, actual types.Ref, srcDB, sinkDB Database) (bool, error) { 379 expectedVal, err := expectad.TargetValue(ctx, srcDB) 380 381 if err != nil { 382 return false, err 383 } 384 385 actualVal, err := actual.TargetValue(ctx, sinkDB) 386 if err != nil { 387 return false, err 388 } 389 390 exPs, exTbls, err := parentsAndTables(expectedVal.(types.Struct)) 391 if err != nil { 392 return false, err 393 } 394 395 actPs, actTbls, err := parentsAndTables(actualVal.(types.Struct)) 396 if err != nil { 397 return false, err 398 } 399 400 if !exPs.Equals(actPs) { 401 return false, nil 402 } 403 404 err = exTbls.IterAll(ctx, func(key, exVal types.Value) error { 405 actVal, ok, err := actTbls.MaybeGet(ctx, key) 406 407 if err != nil { 408 return err 409 } 410 411 if !ok { 412 return errors.New("Missing table " + string(key.(types.String))) 413 } 414 415 exMapVal, err := exVal.(types.Ref).TargetValue(ctx, srcDB) 416 417 if err != nil { 418 return err 419 } 420 421 actMapVal, err := actVal.(types.Ref).TargetValue(ctx, sinkDB) 422 423 if err != nil { 424 return err 425 } 426 427 return errIfNotEqual(ctx, exMapVal.(types.Map), actMapVal.(types.Map)) 428 }) 429 430 if err != nil { 431 return false, err 432 } 433 434 return exTbls.Equals(actTbls), nil 435 } 436 437 var errNotEqual = errors.New("not equal") 438 439 func errIfNotEqual(ctx context.Context, ex, act types.Map) error { 440 exItr, err := ex.Iterator(ctx) 441 442 if err != nil { 443 return err 444 } 445 446 actItr, err := act.Iterator(ctx) 447 448 if err != nil { 449 return err 450 } 451 452 for { 453 exK, exV, err := exItr.Next(ctx) 454 455 if err != nil { 456 return err 457 } 458 459 actK, actV, err := actItr.Next(ctx) 460 461 if err != nil { 462 return err 463 } 464 465 if actK == nil && exK == nil { 466 break 467 } else if exK == nil || actK == nil { 468 return errNotEqual 469 } 470 471 if exV == nil && actV == nil { 472 continue 473 } else if exV == nil || actV == nil { 474 return errNotEqual 475 } 476 477 if !exK.Equals(actK) || !exV.Equals(actV) { 478 return errNotEqual 479 } 480 } 481 482 return nil 483 } 484 485 func parentsAndTables(cm types.Struct) (types.List, types.Map, error) { 486 ps, ok, err := cm.MaybeGet(ParentsListField) 487 488 if err != nil { 489 return types.EmptyList, types.EmptyMap, err 490 } 491 492 if !ok { 493 return types.EmptyList, types.EmptyMap, err 494 } 495 496 tbls, ok, err := cm.MaybeGet("value") 497 498 if err != nil { 499 return types.EmptyList, types.EmptyMap, err 500 } 501 502 if !ok { 503 return types.EmptyList, types.EmptyMap, err 504 } 505 506 return ps.(types.List), tbls.(types.Map), nil 507 } 508 509 func writeValAndGetRef(ctx context.Context, vrw types.ValueReadWriter, val types.Value) (types.Ref, error) { 510 valRef, err := types.NewRef(val, vrw.Format()) 511 512 if err != nil { 513 return types.Ref{}, err 514 } 515 516 targetVal, err := valRef.TargetValue(ctx, vrw) 517 518 if err != nil { 519 return types.Ref{}, err 520 } 521 522 if targetVal == nil { 523 _, err = vrw.WriteValue(ctx, val) 524 525 if err != nil { 526 return types.Ref{}, err 527 } 528 } 529 530 return valRef, err 531 }