swarm: Shed Index and Uint64Field additions (#18398)
This commit is contained in:
		
				
					committed by
					
						 Anton Evangelatov
						Anton Evangelatov
					
				
			
			
				
	
			
			
			
						parent
						
							428eabe28d
						
					
				
				
					commit
					356c49fa7e
				
			| @@ -18,7 +18,7 @@ | |||||||
| // more complex operations on storage data organized in fields and indexes. | // more complex operations on storage data organized in fields and indexes. | ||||||
| // | // | ||||||
| // Only type which holds logical information about swarm storage chunks data | // Only type which holds logical information about swarm storage chunks data | ||||||
| // and metadata is IndexItem. This part is not generalized mostly for | // and metadata is Item. This part is not generalized mostly for | ||||||
| // performance reasons. | // performance reasons. | ||||||
| package shed | package shed | ||||||
|  |  | ||||||
|   | |||||||
| @@ -71,20 +71,20 @@ func New(path string) (s *Store, err error) { | |||||||
| 	} | 	} | ||||||
| 	// Index storing actual chunk address, data and store timestamp. | 	// Index storing actual chunk address, data and store timestamp. | ||||||
| 	s.retrievalIndex, err = db.NewIndex("Address->StoreTimestamp|Data", shed.IndexFuncs{ | 	s.retrievalIndex, err = db.NewIndex("Address->StoreTimestamp|Data", shed.IndexFuncs{ | ||||||
| 		EncodeKey: func(fields shed.IndexItem) (key []byte, err error) { | 		EncodeKey: func(fields shed.Item) (key []byte, err error) { | ||||||
| 			return fields.Address, nil | 			return fields.Address, nil | ||||||
| 		}, | 		}, | ||||||
| 		DecodeKey: func(key []byte) (e shed.IndexItem, err error) { | 		DecodeKey: func(key []byte) (e shed.Item, err error) { | ||||||
| 			e.Address = key | 			e.Address = key | ||||||
| 			return e, nil | 			return e, nil | ||||||
| 		}, | 		}, | ||||||
| 		EncodeValue: func(fields shed.IndexItem) (value []byte, err error) { | 		EncodeValue: func(fields shed.Item) (value []byte, err error) { | ||||||
| 			b := make([]byte, 8) | 			b := make([]byte, 8) | ||||||
| 			binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) | 			binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) | ||||||
| 			value = append(b, fields.Data...) | 			value = append(b, fields.Data...) | ||||||
| 			return value, nil | 			return value, nil | ||||||
| 		}, | 		}, | ||||||
| 		DecodeValue: func(value []byte) (e shed.IndexItem, err error) { | 		DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { | ||||||
| 			e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) | 			e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) | ||||||
| 			e.Data = value[8:] | 			e.Data = value[8:] | ||||||
| 			return e, nil | 			return e, nil | ||||||
| @@ -96,19 +96,19 @@ func New(path string) (s *Store, err error) { | |||||||
| 	// Index storing access timestamp for a particular address. | 	// Index storing access timestamp for a particular address. | ||||||
| 	// It is needed in order to update gc index keys for iteration order. | 	// It is needed in order to update gc index keys for iteration order. | ||||||
| 	s.accessIndex, err = db.NewIndex("Address->AccessTimestamp", shed.IndexFuncs{ | 	s.accessIndex, err = db.NewIndex("Address->AccessTimestamp", shed.IndexFuncs{ | ||||||
| 		EncodeKey: func(fields shed.IndexItem) (key []byte, err error) { | 		EncodeKey: func(fields shed.Item) (key []byte, err error) { | ||||||
| 			return fields.Address, nil | 			return fields.Address, nil | ||||||
| 		}, | 		}, | ||||||
| 		DecodeKey: func(key []byte) (e shed.IndexItem, err error) { | 		DecodeKey: func(key []byte) (e shed.Item, err error) { | ||||||
| 			e.Address = key | 			e.Address = key | ||||||
| 			return e, nil | 			return e, nil | ||||||
| 		}, | 		}, | ||||||
| 		EncodeValue: func(fields shed.IndexItem) (value []byte, err error) { | 		EncodeValue: func(fields shed.Item) (value []byte, err error) { | ||||||
| 			b := make([]byte, 8) | 			b := make([]byte, 8) | ||||||
| 			binary.BigEndian.PutUint64(b, uint64(fields.AccessTimestamp)) | 			binary.BigEndian.PutUint64(b, uint64(fields.AccessTimestamp)) | ||||||
| 			return b, nil | 			return b, nil | ||||||
| 		}, | 		}, | ||||||
| 		DecodeValue: func(value []byte) (e shed.IndexItem, err error) { | 		DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { | ||||||
| 			e.AccessTimestamp = int64(binary.BigEndian.Uint64(value)) | 			e.AccessTimestamp = int64(binary.BigEndian.Uint64(value)) | ||||||
| 			return e, nil | 			return e, nil | ||||||
| 		}, | 		}, | ||||||
| @@ -118,23 +118,23 @@ func New(path string) (s *Store, err error) { | |||||||
| 	} | 	} | ||||||
| 	// Index with keys ordered by access timestamp for garbage collection prioritization. | 	// Index with keys ordered by access timestamp for garbage collection prioritization. | ||||||
| 	s.gcIndex, err = db.NewIndex("AccessTimestamp|StoredTimestamp|Address->nil", shed.IndexFuncs{ | 	s.gcIndex, err = db.NewIndex("AccessTimestamp|StoredTimestamp|Address->nil", shed.IndexFuncs{ | ||||||
| 		EncodeKey: func(fields shed.IndexItem) (key []byte, err error) { | 		EncodeKey: func(fields shed.Item) (key []byte, err error) { | ||||||
| 			b := make([]byte, 16, 16+len(fields.Address)) | 			b := make([]byte, 16, 16+len(fields.Address)) | ||||||
| 			binary.BigEndian.PutUint64(b[:8], uint64(fields.AccessTimestamp)) | 			binary.BigEndian.PutUint64(b[:8], uint64(fields.AccessTimestamp)) | ||||||
| 			binary.BigEndian.PutUint64(b[8:16], uint64(fields.StoreTimestamp)) | 			binary.BigEndian.PutUint64(b[8:16], uint64(fields.StoreTimestamp)) | ||||||
| 			key = append(b, fields.Address...) | 			key = append(b, fields.Address...) | ||||||
| 			return key, nil | 			return key, nil | ||||||
| 		}, | 		}, | ||||||
| 		DecodeKey: func(key []byte) (e shed.IndexItem, err error) { | 		DecodeKey: func(key []byte) (e shed.Item, err error) { | ||||||
| 			e.AccessTimestamp = int64(binary.BigEndian.Uint64(key[:8])) | 			e.AccessTimestamp = int64(binary.BigEndian.Uint64(key[:8])) | ||||||
| 			e.StoreTimestamp = int64(binary.BigEndian.Uint64(key[8:16])) | 			e.StoreTimestamp = int64(binary.BigEndian.Uint64(key[8:16])) | ||||||
| 			e.Address = key[16:] | 			e.Address = key[16:] | ||||||
| 			return e, nil | 			return e, nil | ||||||
| 		}, | 		}, | ||||||
| 		EncodeValue: func(fields shed.IndexItem) (value []byte, err error) { | 		EncodeValue: func(fields shed.Item) (value []byte, err error) { | ||||||
| 			return nil, nil | 			return nil, nil | ||||||
| 		}, | 		}, | ||||||
| 		DecodeValue: func(value []byte) (e shed.IndexItem, err error) { | 		DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { | ||||||
| 			return e, nil | 			return e, nil | ||||||
| 		}, | 		}, | ||||||
| 	}) | 	}) | ||||||
| @@ -146,7 +146,7 @@ func New(path string) (s *Store, err error) { | |||||||
|  |  | ||||||
| // Put stores the chunk and sets it store timestamp. | // Put stores the chunk and sets it store timestamp. | ||||||
| func (s *Store) Put(_ context.Context, ch storage.Chunk) (err error) { | func (s *Store) Put(_ context.Context, ch storage.Chunk) (err error) { | ||||||
| 	return s.retrievalIndex.Put(shed.IndexItem{ | 	return s.retrievalIndex.Put(shed.Item{ | ||||||
| 		Address:        ch.Address(), | 		Address:        ch.Address(), | ||||||
| 		Data:           ch.Data(), | 		Data:           ch.Data(), | ||||||
| 		StoreTimestamp: time.Now().UTC().UnixNano(), | 		StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| @@ -161,7 +161,7 @@ func (s *Store) Get(_ context.Context, addr storage.Address) (c storage.Chunk, e | |||||||
| 	batch := new(leveldb.Batch) | 	batch := new(leveldb.Batch) | ||||||
|  |  | ||||||
| 	// Get the chunk data and storage timestamp. | 	// Get the chunk data and storage timestamp. | ||||||
| 	item, err := s.retrievalIndex.Get(shed.IndexItem{ | 	item, err := s.retrievalIndex.Get(shed.Item{ | ||||||
| 		Address: addr, | 		Address: addr, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -172,13 +172,13 @@ func (s *Store) Get(_ context.Context, addr storage.Address) (c storage.Chunk, e | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Get the chunk access timestamp. | 	// Get the chunk access timestamp. | ||||||
| 	accessItem, err := s.accessIndex.Get(shed.IndexItem{ | 	accessItem, err := s.accessIndex.Get(shed.Item{ | ||||||
| 		Address: addr, | 		Address: addr, | ||||||
| 	}) | 	}) | ||||||
| 	switch err { | 	switch err { | ||||||
| 	case nil: | 	case nil: | ||||||
| 		// Remove gc index entry if access timestamp is found. | 		// Remove gc index entry if access timestamp is found. | ||||||
| 		err = s.gcIndex.DeleteInBatch(batch, shed.IndexItem{ | 		err = s.gcIndex.DeleteInBatch(batch, shed.Item{ | ||||||
| 			Address:         item.Address, | 			Address:         item.Address, | ||||||
| 			StoreTimestamp:  accessItem.AccessTimestamp, | 			StoreTimestamp:  accessItem.AccessTimestamp, | ||||||
| 			AccessTimestamp: item.StoreTimestamp, | 			AccessTimestamp: item.StoreTimestamp, | ||||||
| @@ -197,7 +197,7 @@ func (s *Store) Get(_ context.Context, addr storage.Address) (c storage.Chunk, e | |||||||
| 	accessTimestamp := time.Now().UTC().UnixNano() | 	accessTimestamp := time.Now().UTC().UnixNano() | ||||||
|  |  | ||||||
| 	// Put new access timestamp in access index. | 	// Put new access timestamp in access index. | ||||||
| 	err = s.accessIndex.PutInBatch(batch, shed.IndexItem{ | 	err = s.accessIndex.PutInBatch(batch, shed.Item{ | ||||||
| 		Address:         addr, | 		Address:         addr, | ||||||
| 		AccessTimestamp: accessTimestamp, | 		AccessTimestamp: accessTimestamp, | ||||||
| 	}) | 	}) | ||||||
| @@ -206,7 +206,7 @@ func (s *Store) Get(_ context.Context, addr storage.Address) (c storage.Chunk, e | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Put new access timestamp in gc index. | 	// Put new access timestamp in gc index. | ||||||
| 	err = s.gcIndex.PutInBatch(batch, shed.IndexItem{ | 	err = s.gcIndex.PutInBatch(batch, shed.Item{ | ||||||
| 		Address:         item.Address, | 		Address:         item.Address, | ||||||
| 		AccessTimestamp: accessTimestamp, | 		AccessTimestamp: accessTimestamp, | ||||||
| 		StoreTimestamp:  item.StoreTimestamp, | 		StoreTimestamp:  item.StoreTimestamp, | ||||||
| @@ -244,7 +244,7 @@ func (s *Store) CollectGarbage() (err error) { | |||||||
| 		// New batch for a new cg round. | 		// New batch for a new cg round. | ||||||
| 		trash := new(leveldb.Batch) | 		trash := new(leveldb.Batch) | ||||||
| 		// Iterate through all index items and break when needed. | 		// Iterate through all index items and break when needed. | ||||||
| 		err = s.gcIndex.IterateAll(func(item shed.IndexItem) (stop bool, err error) { | 		err = s.gcIndex.Iterate(func(item shed.Item) (stop bool, err error) { | ||||||
| 			// Remove the chunk. | 			// Remove the chunk. | ||||||
| 			err = s.retrievalIndex.DeleteInBatch(trash, item) | 			err = s.retrievalIndex.DeleteInBatch(trash, item) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -265,7 +265,7 @@ func (s *Store) CollectGarbage() (err error) { | |||||||
| 				return true, nil | 				return true, nil | ||||||
| 			} | 			} | ||||||
| 			return false, nil | 			return false, nil | ||||||
| 		}) | 		}, nil) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -99,6 +99,44 @@ func (f Uint64Field) IncInBatch(batch *leveldb.Batch) (val uint64, err error) { | |||||||
| 	return val, nil | 	return val, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Dec decrements a uint64 value in the database. | ||||||
|  | // This operation is not goroutine save. | ||||||
|  | // The field is protected from overflow to a negative value. | ||||||
|  | func (f Uint64Field) Dec() (val uint64, err error) { | ||||||
|  | 	val, err = f.Get() | ||||||
|  | 	if err != nil { | ||||||
|  | 		if err == leveldb.ErrNotFound { | ||||||
|  | 			val = 0 | ||||||
|  | 		} else { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if val != 0 { | ||||||
|  | 		val-- | ||||||
|  | 	} | ||||||
|  | 	return val, f.Put(val) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // DecInBatch decrements a uint64 value in the batch | ||||||
|  | // by retreiving a value from the database, not the same batch. | ||||||
|  | // This operation is not goroutine save. | ||||||
|  | // The field is protected from overflow to a negative value. | ||||||
|  | func (f Uint64Field) DecInBatch(batch *leveldb.Batch) (val uint64, err error) { | ||||||
|  | 	val, err = f.Get() | ||||||
|  | 	if err != nil { | ||||||
|  | 		if err == leveldb.ErrNotFound { | ||||||
|  | 			val = 0 | ||||||
|  | 		} else { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if val != 0 { | ||||||
|  | 		val-- | ||||||
|  | 	} | ||||||
|  | 	f.PutInBatch(batch, val) | ||||||
|  | 	return val, nil | ||||||
|  | } | ||||||
|  |  | ||||||
| // encode transforms uint64 to 8 byte long | // encode transforms uint64 to 8 byte long | ||||||
| // slice in big endian encoding. | // slice in big endian encoding. | ||||||
| func encodeUint64(val uint64) (b []byte) { | func encodeUint64(val uint64) (b []byte) { | ||||||
|   | |||||||
| @@ -192,3 +192,109 @@ func TestUint64Field_IncInBatch(t *testing.T) { | |||||||
| 		t.Errorf("got uint64 %v, want %v", got, want) | 		t.Errorf("got uint64 %v, want %v", got, want) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // TestUint64Field_Dec validates Dec operation | ||||||
|  | // of the Uint64Field. | ||||||
|  | func TestUint64Field_Dec(t *testing.T) { | ||||||
|  | 	db, cleanupFunc := newTestDB(t) | ||||||
|  | 	defer cleanupFunc() | ||||||
|  |  | ||||||
|  | 	counter, err := db.NewUint64Field("counter") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// test overflow protection | ||||||
|  | 	var want uint64 | ||||||
|  | 	got, err := counter.Dec() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if got != want { | ||||||
|  | 		t.Errorf("got uint64 %v, want %v", got, want) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	want = 32 | ||||||
|  | 	err = counter.Put(want) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	want = 31 | ||||||
|  | 	got, err = counter.Dec() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if got != want { | ||||||
|  | 		t.Errorf("got uint64 %v, want %v", got, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TestUint64Field_DecInBatch validates DecInBatch operation | ||||||
|  | // of the Uint64Field. | ||||||
|  | func TestUint64Field_DecInBatch(t *testing.T) { | ||||||
|  | 	db, cleanupFunc := newTestDB(t) | ||||||
|  | 	defer cleanupFunc() | ||||||
|  |  | ||||||
|  | 	counter, err := db.NewUint64Field("counter") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	batch := new(leveldb.Batch) | ||||||
|  | 	var want uint64 | ||||||
|  | 	got, err := counter.DecInBatch(batch) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if got != want { | ||||||
|  | 		t.Errorf("got uint64 %v, want %v", got, want) | ||||||
|  | 	} | ||||||
|  | 	err = db.WriteBatch(batch) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	got, err = counter.Get() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if got != want { | ||||||
|  | 		t.Errorf("got uint64 %v, want %v", got, want) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	batch2 := new(leveldb.Batch) | ||||||
|  | 	want = 42 | ||||||
|  | 	counter.PutInBatch(batch2, want) | ||||||
|  | 	err = db.WriteBatch(batch2) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	got, err = counter.Get() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if got != want { | ||||||
|  | 		t.Errorf("got uint64 %v, want %v", got, want) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	batch3 := new(leveldb.Batch) | ||||||
|  | 	want = 41 | ||||||
|  | 	got, err = counter.DecInBatch(batch3) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if got != want { | ||||||
|  | 		t.Errorf("got uint64 %v, want %v", got, want) | ||||||
|  | 	} | ||||||
|  | 	err = db.WriteBatch(batch3) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	got, err = counter.Get() | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if got != want { | ||||||
|  | 		t.Errorf("got uint64 %v, want %v", got, want) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -17,22 +17,24 @@ | |||||||
| package shed | package shed | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bytes" | ||||||
|  |  | ||||||
| 	"github.com/syndtr/goleveldb/leveldb" | 	"github.com/syndtr/goleveldb/leveldb" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // IndexItem holds fields relevant to Swarm Chunk data and metadata. | // Item holds fields relevant to Swarm Chunk data and metadata. | ||||||
| // All information required for swarm storage and operations | // All information required for swarm storage and operations | ||||||
| // on that storage must be defined here. | // on that storage must be defined here. | ||||||
| // This structure is logically connected to swarm storage, | // This structure is logically connected to swarm storage, | ||||||
| // the only part of this package that is not generalized, | // the only part of this package that is not generalized, | ||||||
| // mostly for performance reasons. | // mostly for performance reasons. | ||||||
| // | // | ||||||
| // IndexItem is a type that is used for retrieving, storing and encoding | // Item is a type that is used for retrieving, storing and encoding | ||||||
| // chunk data and metadata. It is passed as an argument to Index encoding | // chunk data and metadata. It is passed as an argument to Index encoding | ||||||
| // functions, get function and put function. | // functions, get function and put function. | ||||||
| // But it is also returned with additional data from get function call | // But it is also returned with additional data from get function call | ||||||
| // and as the argument in iterator function definition. | // and as the argument in iterator function definition. | ||||||
| type IndexItem struct { | type Item struct { | ||||||
| 	Address         []byte | 	Address         []byte | ||||||
| 	Data            []byte | 	Data            []byte | ||||||
| 	AccessTimestamp int64 | 	AccessTimestamp int64 | ||||||
| @@ -43,9 +45,9 @@ type IndexItem struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Merge is a helper method to construct a new | // Merge is a helper method to construct a new | ||||||
| // IndexItem by filling up fields with default values | // Item by filling up fields with default values | ||||||
| // of a particular IndexItem with values from another one. | // of a particular Item with values from another one. | ||||||
| func (i IndexItem) Merge(i2 IndexItem) (new IndexItem) { | func (i Item) Merge(i2 Item) (new Item) { | ||||||
| 	if i.Address == nil { | 	if i.Address == nil { | ||||||
| 		i.Address = i2.Address | 		i.Address = i2.Address | ||||||
| 	} | 	} | ||||||
| @@ -67,26 +69,26 @@ func (i IndexItem) Merge(i2 IndexItem) (new IndexItem) { | |||||||
| // Index represents a set of LevelDB key value pairs that have common | // Index represents a set of LevelDB key value pairs that have common | ||||||
| // prefix. It holds functions for encoding and decoding keys and values | // prefix. It holds functions for encoding and decoding keys and values | ||||||
| // to provide transparent actions on saved data which inclide: | // to provide transparent actions on saved data which inclide: | ||||||
| // - getting a particular IndexItem | // - getting a particular Item | ||||||
| // - saving a particular IndexItem | // - saving a particular Item | ||||||
| // - iterating over a sorted LevelDB keys | // - iterating over a sorted LevelDB keys | ||||||
| // It implements IndexIteratorInterface interface. | // It implements IndexIteratorInterface interface. | ||||||
| type Index struct { | type Index struct { | ||||||
| 	db              *DB | 	db              *DB | ||||||
| 	prefix          []byte | 	prefix          []byte | ||||||
| 	encodeKeyFunc   func(fields IndexItem) (key []byte, err error) | 	encodeKeyFunc   func(fields Item) (key []byte, err error) | ||||||
| 	decodeKeyFunc   func(key []byte) (e IndexItem, err error) | 	decodeKeyFunc   func(key []byte) (e Item, err error) | ||||||
| 	encodeValueFunc func(fields IndexItem) (value []byte, err error) | 	encodeValueFunc func(fields Item) (value []byte, err error) | ||||||
| 	decodeValueFunc func(value []byte) (e IndexItem, err error) | 	decodeValueFunc func(keyFields Item, value []byte) (e Item, err error) | ||||||
| } | } | ||||||
|  |  | ||||||
| // IndexFuncs structure defines functions for encoding and decoding | // IndexFuncs structure defines functions for encoding and decoding | ||||||
| // LevelDB keys and values for a specific index. | // LevelDB keys and values for a specific index. | ||||||
| type IndexFuncs struct { | type IndexFuncs struct { | ||||||
| 	EncodeKey   func(fields IndexItem) (key []byte, err error) | 	EncodeKey   func(fields Item) (key []byte, err error) | ||||||
| 	DecodeKey   func(key []byte) (e IndexItem, err error) | 	DecodeKey   func(key []byte) (e Item, err error) | ||||||
| 	EncodeValue func(fields IndexItem) (value []byte, err error) | 	EncodeValue func(fields Item) (value []byte, err error) | ||||||
| 	DecodeValue func(value []byte) (e IndexItem, err error) | 	DecodeValue func(keyFields Item, value []byte) (e Item, err error) | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewIndex returns a new Index instance with defined name and | // NewIndex returns a new Index instance with defined name and | ||||||
| @@ -105,7 +107,7 @@ func (db *DB) NewIndex(name string, funcs IndexFuncs) (f Index, err error) { | |||||||
| 		// by appending the provided index id byte. | 		// by appending the provided index id byte. | ||||||
| 		// This is needed to avoid collisions between keys of different | 		// This is needed to avoid collisions between keys of different | ||||||
| 		// indexes as all index ids are unique. | 		// indexes as all index ids are unique. | ||||||
| 		encodeKeyFunc: func(e IndexItem) (key []byte, err error) { | 		encodeKeyFunc: func(e Item) (key []byte, err error) { | ||||||
| 			key, err = funcs.EncodeKey(e) | 			key, err = funcs.EncodeKey(e) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| @@ -115,7 +117,7 @@ func (db *DB) NewIndex(name string, funcs IndexFuncs) (f Index, err error) { | |||||||
| 		// This function reverses the encodeKeyFunc constructed key | 		// This function reverses the encodeKeyFunc constructed key | ||||||
| 		// to transparently work with index keys without their index ids. | 		// to transparently work with index keys without their index ids. | ||||||
| 		// It assumes that index keys are prefixed with only one byte. | 		// It assumes that index keys are prefixed with only one byte. | ||||||
| 		decodeKeyFunc: func(key []byte) (e IndexItem, err error) { | 		decodeKeyFunc: func(key []byte) (e Item, err error) { | ||||||
| 			return funcs.DecodeKey(key[1:]) | 			return funcs.DecodeKey(key[1:]) | ||||||
| 		}, | 		}, | ||||||
| 		encodeValueFunc: funcs.EncodeValue, | 		encodeValueFunc: funcs.EncodeValue, | ||||||
| @@ -123,10 +125,10 @@ func (db *DB) NewIndex(name string, funcs IndexFuncs) (f Index, err error) { | |||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Get accepts key fields represented as IndexItem to retrieve a | // Get accepts key fields represented as Item to retrieve a | ||||||
| // value from the index and return maximum available information | // value from the index and return maximum available information | ||||||
| // from the index represented as another IndexItem. | // from the index represented as another Item. | ||||||
| func (f Index) Get(keyFields IndexItem) (out IndexItem, err error) { | func (f Index) Get(keyFields Item) (out Item, err error) { | ||||||
| 	key, err := f.encodeKeyFunc(keyFields) | 	key, err := f.encodeKeyFunc(keyFields) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return out, err | 		return out, err | ||||||
| @@ -135,16 +137,16 @@ func (f Index) Get(keyFields IndexItem) (out IndexItem, err error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return out, err | 		return out, err | ||||||
| 	} | 	} | ||||||
| 	out, err = f.decodeValueFunc(value) | 	out, err = f.decodeValueFunc(keyFields, value) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return out, err | 		return out, err | ||||||
| 	} | 	} | ||||||
| 	return out.Merge(keyFields), nil | 	return out.Merge(keyFields), nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Put accepts IndexItem to encode information from it | // Put accepts Item to encode information from it | ||||||
| // and save it to the database. | // and save it to the database. | ||||||
| func (f Index) Put(i IndexItem) (err error) { | func (f Index) Put(i Item) (err error) { | ||||||
| 	key, err := f.encodeKeyFunc(i) | 	key, err := f.encodeKeyFunc(i) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -159,7 +161,7 @@ func (f Index) Put(i IndexItem) (err error) { | |||||||
| // PutInBatch is the same as Put method, but it just | // PutInBatch is the same as Put method, but it just | ||||||
| // saves the key/value pair to the batch instead | // saves the key/value pair to the batch instead | ||||||
| // directly to the database. | // directly to the database. | ||||||
| func (f Index) PutInBatch(batch *leveldb.Batch, i IndexItem) (err error) { | func (f Index) PutInBatch(batch *leveldb.Batch, i Item) (err error) { | ||||||
| 	key, err := f.encodeKeyFunc(i) | 	key, err := f.encodeKeyFunc(i) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -172,9 +174,9 @@ func (f Index) PutInBatch(batch *leveldb.Batch, i IndexItem) (err error) { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Delete accepts IndexItem to remove a key/value pair | // Delete accepts Item to remove a key/value pair | ||||||
| // from the database based on its fields. | // from the database based on its fields. | ||||||
| func (f Index) Delete(keyFields IndexItem) (err error) { | func (f Index) Delete(keyFields Item) (err error) { | ||||||
| 	key, err := f.encodeKeyFunc(keyFields) | 	key, err := f.encodeKeyFunc(keyFields) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -184,7 +186,7 @@ func (f Index) Delete(keyFields IndexItem) (err error) { | |||||||
|  |  | ||||||
| // DeleteInBatch is the same as Delete just the operation | // DeleteInBatch is the same as Delete just the operation | ||||||
| // is performed on the batch instead on the database. | // is performed on the batch instead on the database. | ||||||
| func (f Index) DeleteInBatch(batch *leveldb.Batch, keyFields IndexItem) (err error) { | func (f Index) DeleteInBatch(batch *leveldb.Batch, keyFields Item) (err error) { | ||||||
| 	key, err := f.encodeKeyFunc(keyFields) | 	key, err := f.encodeKeyFunc(keyFields) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| @@ -193,32 +195,71 @@ func (f Index) DeleteInBatch(batch *leveldb.Batch, keyFields IndexItem) (err err | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // IndexIterFunc is a callback on every IndexItem that is decoded | // IndexIterFunc is a callback on every Item that is decoded | ||||||
| // by iterating on an Index keys. | // by iterating on an Index keys. | ||||||
| // By returning a true for stop variable, iteration will | // By returning a true for stop variable, iteration will | ||||||
| // stop, and by returning the error, that error will be | // stop, and by returning the error, that error will be | ||||||
| // propagated to the called iterator method on Index. | // propagated to the called iterator method on Index. | ||||||
| type IndexIterFunc func(item IndexItem) (stop bool, err error) | type IndexIterFunc func(item Item) (stop bool, err error) | ||||||
|  |  | ||||||
| // IterateAll iterates over all keys of the Index. | // IterateOptions defines optional parameters for Iterate function. | ||||||
| func (f Index) IterateAll(fn IndexIterFunc) (err error) { | type IterateOptions struct { | ||||||
|  | 	// StartFrom is the Item to start the iteration from. | ||||||
|  | 	StartFrom *Item | ||||||
|  | 	// If SkipStartFromItem is true, StartFrom item will not | ||||||
|  | 	// be iterated on. | ||||||
|  | 	SkipStartFromItem bool | ||||||
|  | 	// Iterate over items which keys have a common prefix. | ||||||
|  | 	Prefix []byte | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Iterate function iterates over keys of the Index. | ||||||
|  | // If IterateOptions is nil, the iterations is over all keys. | ||||||
|  | func (f Index) Iterate(fn IndexIterFunc, options *IterateOptions) (err error) { | ||||||
|  | 	if options == nil { | ||||||
|  | 		options = new(IterateOptions) | ||||||
|  | 	} | ||||||
|  | 	// construct a prefix with Index prefix and optional common key prefix | ||||||
|  | 	prefix := append(f.prefix, options.Prefix...) | ||||||
|  | 	// start from the prefix | ||||||
|  | 	startKey := prefix | ||||||
|  | 	if options.StartFrom != nil { | ||||||
|  | 		// start from the provided StartFrom Item key value | ||||||
|  | 		startKey, err = f.encodeKeyFunc(*options.StartFrom) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	it := f.db.NewIterator() | 	it := f.db.NewIterator() | ||||||
| 	defer it.Release() | 	defer it.Release() | ||||||
|  |  | ||||||
| 	for ok := it.Seek(f.prefix); ok; ok = it.Next() { | 	// move the cursor to the start key | ||||||
|  | 	ok := it.Seek(startKey) | ||||||
|  | 	if !ok { | ||||||
|  | 		// stop iterator if seek has failed | ||||||
|  | 		return it.Error() | ||||||
|  | 	} | ||||||
|  | 	if options.SkipStartFromItem && bytes.Equal(startKey, it.Key()) { | ||||||
|  | 		// skip the start from Item if it is the first key | ||||||
|  | 		// and it is explicitly configured to skip it | ||||||
|  | 		ok = it.Next() | ||||||
|  | 	} | ||||||
|  | 	for ; ok; ok = it.Next() { | ||||||
| 		key := it.Key() | 		key := it.Key() | ||||||
| 		if key[0] != f.prefix[0] { | 		if !bytes.HasPrefix(key, prefix) { | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| 		keyIndexItem, err := f.decodeKeyFunc(key) | 		// create a copy of key byte slice not to share leveldb underlaying slice array | ||||||
|  | 		keyItem, err := f.decodeKeyFunc(append([]byte(nil), key...)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		valueIndexItem, err := f.decodeValueFunc(it.Value()) | 		// create a copy of value byte slice not to share leveldb underlaying slice array | ||||||
|  | 		valueItem, err := f.decodeValueFunc(keyItem, append([]byte(nil), it.Value()...)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 		stop, err := fn(keyIndexItem.Merge(valueIndexItem)) | 		stop, err := fn(keyItem.Merge(valueItem)) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| @@ -229,12 +270,27 @@ func (f Index) IterateAll(fn IndexIterFunc) (err error) { | |||||||
| 	return it.Error() | 	return it.Error() | ||||||
| } | } | ||||||
|  |  | ||||||
| // IterateFrom iterates over Index keys starting from the key | // Count returns the number of items in index. | ||||||
| // encoded from the provided IndexItem. | func (f Index) Count() (count int, err error) { | ||||||
| func (f Index) IterateFrom(start IndexItem, fn IndexIterFunc) (err error) { | 	it := f.db.NewIterator() | ||||||
|  | 	defer it.Release() | ||||||
|  |  | ||||||
|  | 	for ok := it.Seek(f.prefix); ok; ok = it.Next() { | ||||||
|  | 		key := it.Key() | ||||||
|  | 		if key[0] != f.prefix[0] { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		count++ | ||||||
|  | 	} | ||||||
|  | 	return count, it.Error() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CountFrom returns the number of items in index keys | ||||||
|  | // starting from the key encoded from the provided Item. | ||||||
|  | func (f Index) CountFrom(start Item) (count int, err error) { | ||||||
| 	startKey, err := f.encodeKeyFunc(start) | 	startKey, err := f.encodeKeyFunc(start) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return 0, err | ||||||
| 	} | 	} | ||||||
| 	it := f.db.NewIterator() | 	it := f.db.NewIterator() | ||||||
| 	defer it.Release() | 	defer it.Release() | ||||||
| @@ -244,21 +300,7 @@ func (f Index) IterateFrom(start IndexItem, fn IndexIterFunc) (err error) { | |||||||
| 		if key[0] != f.prefix[0] { | 		if key[0] != f.prefix[0] { | ||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| 		keyIndexItem, err := f.decodeKeyFunc(key) | 		count++ | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		valueIndexItem, err := f.decodeValueFunc(it.Value()) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		stop, err := fn(keyIndexItem.Merge(valueIndexItem)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		if stop { |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	return it.Error() | 	return count, it.Error() | ||||||
| } | } | ||||||
|   | |||||||
| @@ -29,20 +29,20 @@ import ( | |||||||
|  |  | ||||||
| // Index functions for the index that is used in tests in this file. | // Index functions for the index that is used in tests in this file. | ||||||
| var retrievalIndexFuncs = IndexFuncs{ | var retrievalIndexFuncs = IndexFuncs{ | ||||||
| 	EncodeKey: func(fields IndexItem) (key []byte, err error) { | 	EncodeKey: func(fields Item) (key []byte, err error) { | ||||||
| 		return fields.Address, nil | 		return fields.Address, nil | ||||||
| 	}, | 	}, | ||||||
| 	DecodeKey: func(key []byte) (e IndexItem, err error) { | 	DecodeKey: func(key []byte) (e Item, err error) { | ||||||
| 		e.Address = key | 		e.Address = key | ||||||
| 		return e, nil | 		return e, nil | ||||||
| 	}, | 	}, | ||||||
| 	EncodeValue: func(fields IndexItem) (value []byte, err error) { | 	EncodeValue: func(fields Item) (value []byte, err error) { | ||||||
| 		b := make([]byte, 8) | 		b := make([]byte, 8) | ||||||
| 		binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) | 		binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) | ||||||
| 		value = append(b, fields.Data...) | 		value = append(b, fields.Data...) | ||||||
| 		return value, nil | 		return value, nil | ||||||
| 	}, | 	}, | ||||||
| 	DecodeValue: func(value []byte) (e IndexItem, err error) { | 	DecodeValue: func(keyItem Item, value []byte) (e Item, err error) { | ||||||
| 		e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) | 		e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) | ||||||
| 		e.Data = value[8:] | 		e.Data = value[8:] | ||||||
| 		return e, nil | 		return e, nil | ||||||
| @@ -60,7 +60,7 @@ func TestIndex(t *testing.T) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	t.Run("put", func(t *testing.T) { | 	t.Run("put", func(t *testing.T) { | ||||||
| 		want := IndexItem{ | 		want := Item{ | ||||||
| 			Address:        []byte("put-hash"), | 			Address:        []byte("put-hash"), | ||||||
| 			Data:           []byte("DATA"), | 			Data:           []byte("DATA"), | ||||||
| 			StoreTimestamp: time.Now().UTC().UnixNano(), | 			StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| @@ -70,16 +70,16 @@ func TestIndex(t *testing.T) { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		got, err := index.Get(IndexItem{ | 		got, err := index.Get(Item{ | ||||||
| 			Address: want.Address, | 			Address: want.Address, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		checkIndexItem(t, got, want) | 		checkItem(t, got, want) | ||||||
|  |  | ||||||
| 		t.Run("overwrite", func(t *testing.T) { | 		t.Run("overwrite", func(t *testing.T) { | ||||||
| 			want := IndexItem{ | 			want := Item{ | ||||||
| 				Address:        []byte("put-hash"), | 				Address:        []byte("put-hash"), | ||||||
| 				Data:           []byte("New DATA"), | 				Data:           []byte("New DATA"), | ||||||
| 				StoreTimestamp: time.Now().UTC().UnixNano(), | 				StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| @@ -89,18 +89,18 @@ func TestIndex(t *testing.T) { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			got, err := index.Get(IndexItem{ | 			got, err := index.Get(Item{ | ||||||
| 				Address: want.Address, | 				Address: want.Address, | ||||||
| 			}) | 			}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			checkIndexItem(t, got, want) | 			checkItem(t, got, want) | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("put in batch", func(t *testing.T) { | 	t.Run("put in batch", func(t *testing.T) { | ||||||
| 		want := IndexItem{ | 		want := Item{ | ||||||
| 			Address:        []byte("put-in-batch-hash"), | 			Address:        []byte("put-in-batch-hash"), | ||||||
| 			Data:           []byte("DATA"), | 			Data:           []byte("DATA"), | ||||||
| 			StoreTimestamp: time.Now().UTC().UnixNano(), | 			StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| @@ -112,16 +112,16 @@ func TestIndex(t *testing.T) { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		got, err := index.Get(IndexItem{ | 		got, err := index.Get(Item{ | ||||||
| 			Address: want.Address, | 			Address: want.Address, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		checkIndexItem(t, got, want) | 		checkItem(t, got, want) | ||||||
|  |  | ||||||
| 		t.Run("overwrite", func(t *testing.T) { | 		t.Run("overwrite", func(t *testing.T) { | ||||||
| 			want := IndexItem{ | 			want := Item{ | ||||||
| 				Address:        []byte("put-in-batch-hash"), | 				Address:        []byte("put-in-batch-hash"), | ||||||
| 				Data:           []byte("New DATA"), | 				Data:           []byte("New DATA"), | ||||||
| 				StoreTimestamp: time.Now().UTC().UnixNano(), | 				StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| @@ -133,13 +133,13 @@ func TestIndex(t *testing.T) { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			got, err := index.Get(IndexItem{ | 			got, err := index.Get(Item{ | ||||||
| 				Address: want.Address, | 				Address: want.Address, | ||||||
| 			}) | 			}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			checkIndexItem(t, got, want) | 			checkItem(t, got, want) | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| @@ -150,13 +150,13 @@ func TestIndex(t *testing.T) { | |||||||
| 		address := []byte("put-in-batch-twice-hash") | 		address := []byte("put-in-batch-twice-hash") | ||||||
|  |  | ||||||
| 		// put the first item | 		// put the first item | ||||||
| 		index.PutInBatch(batch, IndexItem{ | 		index.PutInBatch(batch, Item{ | ||||||
| 			Address:        address, | 			Address:        address, | ||||||
| 			Data:           []byte("DATA"), | 			Data:           []byte("DATA"), | ||||||
| 			StoreTimestamp: time.Now().UTC().UnixNano(), | 			StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		want := IndexItem{ | 		want := Item{ | ||||||
| 			Address:        address, | 			Address:        address, | ||||||
| 			Data:           []byte("New DATA"), | 			Data:           []byte("New DATA"), | ||||||
| 			StoreTimestamp: time.Now().UTC().UnixNano(), | 			StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| @@ -168,17 +168,17 @@ func TestIndex(t *testing.T) { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		got, err := index.Get(IndexItem{ | 		got, err := index.Get(Item{ | ||||||
| 			Address: address, | 			Address: address, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		checkIndexItem(t, got, want) | 		checkItem(t, got, want) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("delete", func(t *testing.T) { | 	t.Run("delete", func(t *testing.T) { | ||||||
| 		want := IndexItem{ | 		want := Item{ | ||||||
| 			Address:        []byte("delete-hash"), | 			Address:        []byte("delete-hash"), | ||||||
| 			Data:           []byte("DATA"), | 			Data:           []byte("DATA"), | ||||||
| 			StoreTimestamp: time.Now().UTC().UnixNano(), | 			StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| @@ -188,15 +188,15 @@ func TestIndex(t *testing.T) { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		got, err := index.Get(IndexItem{ | 		got, err := index.Get(Item{ | ||||||
| 			Address: want.Address, | 			Address: want.Address, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		checkIndexItem(t, got, want) | 		checkItem(t, got, want) | ||||||
|  |  | ||||||
| 		err = index.Delete(IndexItem{ | 		err = index.Delete(Item{ | ||||||
| 			Address: want.Address, | 			Address: want.Address, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| @@ -204,7 +204,7 @@ func TestIndex(t *testing.T) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		wantErr := leveldb.ErrNotFound | 		wantErr := leveldb.ErrNotFound | ||||||
| 		got, err = index.Get(IndexItem{ | 		got, err = index.Get(Item{ | ||||||
| 			Address: want.Address, | 			Address: want.Address, | ||||||
| 		}) | 		}) | ||||||
| 		if err != wantErr { | 		if err != wantErr { | ||||||
| @@ -213,7 +213,7 @@ func TestIndex(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("delete in batch", func(t *testing.T) { | 	t.Run("delete in batch", func(t *testing.T) { | ||||||
| 		want := IndexItem{ | 		want := Item{ | ||||||
| 			Address:        []byte("delete-in-batch-hash"), | 			Address:        []byte("delete-in-batch-hash"), | ||||||
| 			Data:           []byte("DATA"), | 			Data:           []byte("DATA"), | ||||||
| 			StoreTimestamp: time.Now().UTC().UnixNano(), | 			StoreTimestamp: time.Now().UTC().UnixNano(), | ||||||
| @@ -223,16 +223,16 @@ func TestIndex(t *testing.T) { | |||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		got, err := index.Get(IndexItem{ | 		got, err := index.Get(Item{ | ||||||
| 			Address: want.Address, | 			Address: want.Address, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 		checkIndexItem(t, got, want) | 		checkItem(t, got, want) | ||||||
|  |  | ||||||
| 		batch := new(leveldb.Batch) | 		batch := new(leveldb.Batch) | ||||||
| 		index.DeleteInBatch(batch, IndexItem{ | 		index.DeleteInBatch(batch, Item{ | ||||||
| 			Address: want.Address, | 			Address: want.Address, | ||||||
| 		}) | 		}) | ||||||
| 		err = db.WriteBatch(batch) | 		err = db.WriteBatch(batch) | ||||||
| @@ -241,7 +241,7 @@ func TestIndex(t *testing.T) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		wantErr := leveldb.ErrNotFound | 		wantErr := leveldb.ErrNotFound | ||||||
| 		got, err = index.Get(IndexItem{ | 		got, err = index.Get(Item{ | ||||||
| 			Address: want.Address, | 			Address: want.Address, | ||||||
| 		}) | 		}) | ||||||
| 		if err != wantErr { | 		if err != wantErr { | ||||||
| @@ -250,8 +250,9 @@ func TestIndex(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // TestIndex_iterate validates index iterator functions for correctness. | // TestIndex_Iterate validates index Iterate | ||||||
| func TestIndex_iterate(t *testing.T) { | // functions for correctness. | ||||||
|  | func TestIndex_Iterate(t *testing.T) { | ||||||
| 	db, cleanupFunc := newTestDB(t) | 	db, cleanupFunc := newTestDB(t) | ||||||
| 	defer cleanupFunc() | 	defer cleanupFunc() | ||||||
|  |  | ||||||
| @@ -260,7 +261,7 @@ func TestIndex_iterate(t *testing.T) { | |||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	items := []IndexItem{ | 	items := []Item{ | ||||||
| 		{ | 		{ | ||||||
| 			Address: []byte("iterate-hash-01"), | 			Address: []byte("iterate-hash-01"), | ||||||
| 			Data:    []byte("data80"), | 			Data:    []byte("data80"), | ||||||
| @@ -290,7 +291,7 @@ func TestIndex_iterate(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatal(err) | 		t.Fatal(err) | ||||||
| 	} | 	} | ||||||
| 	item04 := IndexItem{ | 	item04 := Item{ | ||||||
| 		Address: []byte("iterate-hash-04"), | 		Address: []byte("iterate-hash-04"), | ||||||
| 		Data:    []byte("data0"), | 		Data:    []byte("data0"), | ||||||
| 	} | 	} | ||||||
| @@ -306,31 +307,53 @@ func TestIndex_iterate(t *testing.T) { | |||||||
|  |  | ||||||
| 	t.Run("all", func(t *testing.T) { | 	t.Run("all", func(t *testing.T) { | ||||||
| 		var i int | 		var i int | ||||||
| 		err := index.IterateAll(func(item IndexItem) (stop bool, err error) { | 		err := index.Iterate(func(item Item) (stop bool, err error) { | ||||||
| 			if i > len(items)-1 { | 			if i > len(items)-1 { | ||||||
| 				return true, fmt.Errorf("got unexpected index item: %#v", item) | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
| 			} | 			} | ||||||
| 			want := items[i] | 			want := items[i] | ||||||
| 			checkIndexItem(t, item, want) | 			checkItem(t, item, want) | ||||||
| 			i++ | 			i++ | ||||||
| 			return false, nil | 			return false, nil | ||||||
|  | 		}, nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("start from", func(t *testing.T) { | ||||||
|  | 		startIndex := 2 | ||||||
|  | 		i := startIndex | ||||||
|  | 		err := index.Iterate(func(item Item) (stop bool, err error) { | ||||||
|  | 			if i > len(items)-1 { | ||||||
|  | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
|  | 			} | ||||||
|  | 			want := items[i] | ||||||
|  | 			checkItem(t, item, want) | ||||||
|  | 			i++ | ||||||
|  | 			return false, nil | ||||||
|  | 		}, &IterateOptions{ | ||||||
|  | 			StartFrom: &items[startIndex], | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("from", func(t *testing.T) { | 	t.Run("skip start from", func(t *testing.T) { | ||||||
| 		startIndex := 2 | 		startIndex := 2 | ||||||
| 		i := startIndex | 		i := startIndex + 1 | ||||||
| 		err := index.IterateFrom(items[startIndex], func(item IndexItem) (stop bool, err error) { | 		err := index.Iterate(func(item Item) (stop bool, err error) { | ||||||
| 			if i > len(items)-1 { | 			if i > len(items)-1 { | ||||||
| 				return true, fmt.Errorf("got unexpected index item: %#v", item) | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
| 			} | 			} | ||||||
| 			want := items[i] | 			want := items[i] | ||||||
| 			checkIndexItem(t, item, want) | 			checkItem(t, item, want) | ||||||
| 			i++ | 			i++ | ||||||
| 			return false, nil | 			return false, nil | ||||||
|  | 		}, &IterateOptions{ | ||||||
|  | 			StartFrom:         &items[startIndex], | ||||||
|  | 			SkipStartFromItem: true, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| @@ -341,18 +364,209 @@ func TestIndex_iterate(t *testing.T) { | |||||||
| 		var i int | 		var i int | ||||||
| 		stopIndex := 3 | 		stopIndex := 3 | ||||||
| 		var count int | 		var count int | ||||||
| 		err := index.IterateAll(func(item IndexItem) (stop bool, err error) { | 		err := index.Iterate(func(item Item) (stop bool, err error) { | ||||||
| 			if i > len(items)-1 { | 			if i > len(items)-1 { | ||||||
| 				return true, fmt.Errorf("got unexpected index item: %#v", item) | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
| 			} | 			} | ||||||
| 			want := items[i] | 			want := items[i] | ||||||
| 			checkIndexItem(t, item, want) | 			checkItem(t, item, want) | ||||||
| 			count++ | 			count++ | ||||||
| 			if i == stopIndex { | 			if i == stopIndex { | ||||||
| 				return true, nil | 				return true, nil | ||||||
| 			} | 			} | ||||||
| 			i++ | 			i++ | ||||||
| 			return false, nil | 			return false, nil | ||||||
|  | 		}, nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		wantItemsCount := stopIndex + 1 | ||||||
|  | 		if count != wantItemsCount { | ||||||
|  | 			t.Errorf("got %v items, expected %v", count, wantItemsCount) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("no overflow", func(t *testing.T) { | ||||||
|  | 		secondIndex, err := db.NewIndex("second-index", retrievalIndexFuncs) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		secondItem := Item{ | ||||||
|  | 			Address: []byte("iterate-hash-10"), | ||||||
|  | 			Data:    []byte("data-second"), | ||||||
|  | 		} | ||||||
|  | 		err = secondIndex.Put(secondItem) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		var i int | ||||||
|  | 		err = index.Iterate(func(item Item) (stop bool, err error) { | ||||||
|  | 			if i > len(items)-1 { | ||||||
|  | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
|  | 			} | ||||||
|  | 			want := items[i] | ||||||
|  | 			checkItem(t, item, want) | ||||||
|  | 			i++ | ||||||
|  | 			return false, nil | ||||||
|  | 		}, nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		i = 0 | ||||||
|  | 		err = secondIndex.Iterate(func(item Item) (stop bool, err error) { | ||||||
|  | 			if i > 1 { | ||||||
|  | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
|  | 			} | ||||||
|  | 			checkItem(t, item, secondItem) | ||||||
|  | 			i++ | ||||||
|  | 			return false, nil | ||||||
|  | 		}, nil) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // TestIndex_Iterate_withPrefix validates index Iterate | ||||||
|  | // function for correctness. | ||||||
|  | func TestIndex_Iterate_withPrefix(t *testing.T) { | ||||||
|  | 	db, cleanupFunc := newTestDB(t) | ||||||
|  | 	defer cleanupFunc() | ||||||
|  |  | ||||||
|  | 	index, err := db.NewIndex("retrieval", retrievalIndexFuncs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	allItems := []Item{ | ||||||
|  | 		{Address: []byte("want-hash-00"), Data: []byte("data80")}, | ||||||
|  | 		{Address: []byte("skip-hash-01"), Data: []byte("data81")}, | ||||||
|  | 		{Address: []byte("skip-hash-02"), Data: []byte("data82")}, | ||||||
|  | 		{Address: []byte("skip-hash-03"), Data: []byte("data83")}, | ||||||
|  | 		{Address: []byte("want-hash-04"), Data: []byte("data84")}, | ||||||
|  | 		{Address: []byte("want-hash-05"), Data: []byte("data85")}, | ||||||
|  | 		{Address: []byte("want-hash-06"), Data: []byte("data86")}, | ||||||
|  | 		{Address: []byte("want-hash-07"), Data: []byte("data87")}, | ||||||
|  | 		{Address: []byte("want-hash-08"), Data: []byte("data88")}, | ||||||
|  | 		{Address: []byte("want-hash-09"), Data: []byte("data89")}, | ||||||
|  | 		{Address: []byte("skip-hash-10"), Data: []byte("data90")}, | ||||||
|  | 	} | ||||||
|  | 	batch := new(leveldb.Batch) | ||||||
|  | 	for _, i := range allItems { | ||||||
|  | 		index.PutInBatch(batch, i) | ||||||
|  | 	} | ||||||
|  | 	err = db.WriteBatch(batch) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	prefix := []byte("want") | ||||||
|  |  | ||||||
|  | 	items := make([]Item, 0) | ||||||
|  | 	for _, item := range allItems { | ||||||
|  | 		if bytes.HasPrefix(item.Address, prefix) { | ||||||
|  | 			items = append(items, item) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	sort.SliceStable(items, func(i, j int) bool { | ||||||
|  | 		return bytes.Compare(items[i].Address, items[j].Address) < 0 | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("with prefix", func(t *testing.T) { | ||||||
|  | 		var i int | ||||||
|  | 		err := index.Iterate(func(item Item) (stop bool, err error) { | ||||||
|  | 			if i > len(items)-1 { | ||||||
|  | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
|  | 			} | ||||||
|  | 			want := items[i] | ||||||
|  | 			checkItem(t, item, want) | ||||||
|  | 			i++ | ||||||
|  | 			return false, nil | ||||||
|  | 		}, &IterateOptions{ | ||||||
|  | 			Prefix: prefix, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		if i != len(items) { | ||||||
|  | 			t.Errorf("got %v items, want %v", i, len(items)) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("with prefix and start from", func(t *testing.T) { | ||||||
|  | 		startIndex := 2 | ||||||
|  | 		var count int | ||||||
|  | 		i := startIndex | ||||||
|  | 		err := index.Iterate(func(item Item) (stop bool, err error) { | ||||||
|  | 			if i > len(items)-1 { | ||||||
|  | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
|  | 			} | ||||||
|  | 			want := items[i] | ||||||
|  | 			checkItem(t, item, want) | ||||||
|  | 			i++ | ||||||
|  | 			count++ | ||||||
|  | 			return false, nil | ||||||
|  | 		}, &IterateOptions{ | ||||||
|  | 			StartFrom: &items[startIndex], | ||||||
|  | 			Prefix:    prefix, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		wantCount := len(items) - startIndex | ||||||
|  | 		if count != wantCount { | ||||||
|  | 			t.Errorf("got %v items, want %v", count, wantCount) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("with prefix and skip start from", func(t *testing.T) { | ||||||
|  | 		startIndex := 2 | ||||||
|  | 		var count int | ||||||
|  | 		i := startIndex + 1 | ||||||
|  | 		err := index.Iterate(func(item Item) (stop bool, err error) { | ||||||
|  | 			if i > len(items)-1 { | ||||||
|  | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
|  | 			} | ||||||
|  | 			want := items[i] | ||||||
|  | 			checkItem(t, item, want) | ||||||
|  | 			i++ | ||||||
|  | 			count++ | ||||||
|  | 			return false, nil | ||||||
|  | 		}, &IterateOptions{ | ||||||
|  | 			StartFrom:         &items[startIndex], | ||||||
|  | 			SkipStartFromItem: true, | ||||||
|  | 			Prefix:            prefix, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  | 		wantCount := len(items) - startIndex - 1 | ||||||
|  | 		if count != wantCount { | ||||||
|  | 			t.Errorf("got %v items, want %v", count, wantCount) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("stop", func(t *testing.T) { | ||||||
|  | 		var i int | ||||||
|  | 		stopIndex := 3 | ||||||
|  | 		var count int | ||||||
|  | 		err := index.Iterate(func(item Item) (stop bool, err error) { | ||||||
|  | 			if i > len(items)-1 { | ||||||
|  | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
|  | 			} | ||||||
|  | 			want := items[i] | ||||||
|  | 			checkItem(t, item, want) | ||||||
|  | 			count++ | ||||||
|  | 			if i == stopIndex { | ||||||
|  | 				return true, nil | ||||||
|  | 			} | ||||||
|  | 			i++ | ||||||
|  | 			return false, nil | ||||||
|  | 		}, &IterateOptions{ | ||||||
|  | 			Prefix: prefix, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| @@ -369,46 +583,187 @@ func TestIndex_iterate(t *testing.T) { | |||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		secondIndexItem := IndexItem{ | 		secondItem := Item{ | ||||||
| 			Address: []byte("iterate-hash-10"), | 			Address: []byte("iterate-hash-10"), | ||||||
| 			Data:    []byte("data-second"), | 			Data:    []byte("data-second"), | ||||||
| 		} | 		} | ||||||
| 		err = secondIndex.Put(secondIndexItem) | 		err = secondIndex.Put(secondItem) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		var i int | 		var i int | ||||||
| 		err = index.IterateAll(func(item IndexItem) (stop bool, err error) { | 		err = index.Iterate(func(item Item) (stop bool, err error) { | ||||||
| 			if i > len(items)-1 { | 			if i > len(items)-1 { | ||||||
| 				return true, fmt.Errorf("got unexpected index item: %#v", item) | 				return true, fmt.Errorf("got unexpected index item: %#v", item) | ||||||
| 			} | 			} | ||||||
| 			want := items[i] | 			want := items[i] | ||||||
| 			checkIndexItem(t, item, want) | 			checkItem(t, item, want) | ||||||
| 			i++ | 			i++ | ||||||
| 			return false, nil | 			return false, nil | ||||||
|  | 		}, &IterateOptions{ | ||||||
|  | 			Prefix: prefix, | ||||||
| 		}) | 		}) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Fatal(err) | 			t.Fatal(err) | ||||||
| 		} | 		} | ||||||
|  | 		if i != len(items) { | ||||||
| 		i = 0 | 			t.Errorf("got %v items, want %v", i, len(items)) | ||||||
| 		err = secondIndex.IterateAll(func(item IndexItem) (stop bool, err error) { |  | ||||||
| 			if i > 1 { |  | ||||||
| 				return true, fmt.Errorf("got unexpected index item: %#v", item) |  | ||||||
| 			} |  | ||||||
| 			checkIndexItem(t, item, secondIndexItem) |  | ||||||
| 			i++ |  | ||||||
| 			return false, nil |  | ||||||
| 		}) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Fatal(err) |  | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|  |  | ||||||
| // checkIndexItem is a test helper function that compares if two Index items are the same. | // TestIndex_count tests if Index.Count and Index.CountFrom | ||||||
| func checkIndexItem(t *testing.T, got, want IndexItem) { | // returns the correct number of items. | ||||||
|  | func TestIndex_count(t *testing.T) { | ||||||
|  | 	db, cleanupFunc := newTestDB(t) | ||||||
|  | 	defer cleanupFunc() | ||||||
|  |  | ||||||
|  | 	index, err := db.NewIndex("retrieval", retrievalIndexFuncs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	items := []Item{ | ||||||
|  | 		{ | ||||||
|  | 			Address: []byte("iterate-hash-01"), | ||||||
|  | 			Data:    []byte("data80"), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Address: []byte("iterate-hash-02"), | ||||||
|  | 			Data:    []byte("data84"), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Address: []byte("iterate-hash-03"), | ||||||
|  | 			Data:    []byte("data22"), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Address: []byte("iterate-hash-04"), | ||||||
|  | 			Data:    []byte("data41"), | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Address: []byte("iterate-hash-05"), | ||||||
|  | 			Data:    []byte("data1"), | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	batch := new(leveldb.Batch) | ||||||
|  | 	for _, i := range items { | ||||||
|  | 		index.PutInBatch(batch, i) | ||||||
|  | 	} | ||||||
|  | 	err = db.WriteBatch(batch) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Run("Count", func(t *testing.T) { | ||||||
|  | 		got, err := index.Count() | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		want := len(items) | ||||||
|  | 		if got != want { | ||||||
|  | 			t.Errorf("got %v items count, want %v", got, want) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	t.Run("CountFrom", func(t *testing.T) { | ||||||
|  | 		got, err := index.CountFrom(Item{ | ||||||
|  | 			Address: items[1].Address, | ||||||
|  | 		}) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		want := len(items) - 1 | ||||||
|  | 		if got != want { | ||||||
|  | 			t.Errorf("got %v items count, want %v", got, want) | ||||||
|  | 		} | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	// update the index with another item | ||||||
|  | 	t.Run("add item", func(t *testing.T) { | ||||||
|  | 		item04 := Item{ | ||||||
|  | 			Address: []byte("iterate-hash-06"), | ||||||
|  | 			Data:    []byte("data0"), | ||||||
|  | 		} | ||||||
|  | 		err = index.Put(item04) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		count := len(items) + 1 | ||||||
|  |  | ||||||
|  | 		t.Run("Count", func(t *testing.T) { | ||||||
|  | 			got, err := index.Count() | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatal(err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			want := count | ||||||
|  | 			if got != want { | ||||||
|  | 				t.Errorf("got %v items count, want %v", got, want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		t.Run("CountFrom", func(t *testing.T) { | ||||||
|  | 			got, err := index.CountFrom(Item{ | ||||||
|  | 				Address: items[1].Address, | ||||||
|  | 			}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatal(err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			want := count - 1 | ||||||
|  | 			if got != want { | ||||||
|  | 				t.Errorf("got %v items count, want %v", got, want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	// delete some items | ||||||
|  | 	t.Run("delete items", func(t *testing.T) { | ||||||
|  | 		deleteCount := 3 | ||||||
|  |  | ||||||
|  | 		for _, item := range items[:deleteCount] { | ||||||
|  | 			err := index.Delete(item) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatal(err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		count := len(items) + 1 - deleteCount | ||||||
|  |  | ||||||
|  | 		t.Run("Count", func(t *testing.T) { | ||||||
|  | 			got, err := index.Count() | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatal(err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			want := count | ||||||
|  | 			if got != want { | ||||||
|  | 				t.Errorf("got %v items count, want %v", got, want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		t.Run("CountFrom", func(t *testing.T) { | ||||||
|  | 			got, err := index.CountFrom(Item{ | ||||||
|  | 				Address: items[deleteCount+1].Address, | ||||||
|  | 			}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatal(err) | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			want := count - 1 | ||||||
|  | 			if got != want { | ||||||
|  | 				t.Errorf("got %v items count, want %v", got, want) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // checkItem is a test helper function that compares if two Index items are the same. | ||||||
|  | func checkItem(t *testing.T, got, want Item) { | ||||||
| 	t.Helper() | 	t.Helper() | ||||||
|  |  | ||||||
| 	if !bytes.Equal(got.Address, want.Address) { | 	if !bytes.Equal(got.Address, want.Address) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user