Skip to content

Commit

Permalink
Merge pull request ByteStorage#229 from saeid-a/feat/struct-set
Browse files Browse the repository at this point in the history
Feat/struct set (ByteStorage#138)
  • Loading branch information
qishenonly authored Jul 23, 2023
2 parents 0076122 + af57531 commit 3f06b04
Show file tree
Hide file tree
Showing 2 changed files with 1,456 additions and 0 deletions.
397 changes: 397 additions & 0 deletions structure/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,397 @@
package structure

import (
"errors"
"github.com/ByteStorage/FlyDB/config"
"github.com/ByteStorage/FlyDB/engine"
_const "github.com/ByteStorage/FlyDB/lib/const"
"github.com/ByteStorage/FlyDB/lib/encoding"
)

type SetStructure struct {
db *engine.DB
}

var (
ErrMemberNotFound = errors.New("ErrMemberNotFound: member not found")
ErrSetNotInitialized = errors.New("wrong operation: set not initialized")
)

// FSets serves as a set data structure where every key in the map is an element of the set.
type FSets map[string]struct{}

func NewSetStructure(options config.Options) (*SetStructure, error) {
db, err := engine.NewDB(options)
if err != nil {
return nil, err
}
return &SetStructure{db: db}, nil
}

// SAdd adds a member to the set stored at key.
//
// If the set did not exist, a new set will be created
// and the member will be added to it.
func (s *SetStructure) SAdd(key, member string) error {
return s.SAdds(key, member)
}

/*
SAdds attempts to add multiple members to a set identified by the 'key' parameter. The members to add are variadic and come as 'members...' in the function signature. This means the function accepts an arbitrary number of strings to be added to the set.
Parameters:
- 's': Pointer to an instance of SetStructure. This is the receiver of the SAdds function.
- 'key': String that is used as the identifier for the set.
- 'members': Variadic parameter that represents the members to be added. Since it's a variadic parameter, it can be any number of strings.
Return:
- It returns an error if it encounters one during execution. Errors might occur if the key is found to be empty or if problems arise when retrieving the set from the database or saving it back.
Internal logic:
1. The function first checks if the provided key is an empty string. If so, it returns an error indicating that the key is empty.
2. Converts the key into bytes format for internal use.
3. It tries to get the set associated with the key from the database. If it encounters an error during retrieval, it returns that error.
4. If it can successfully retrieve the set, it attempts to add the members to the set.
5. After adding the members, it tries to save this updated set back into the database. If this operation throws an error, it returns that error.
All the methods like 'getZSetFromDB', 'setZSetToDB', and 'add' handle the lower-level logic associated with database interaction and set manipulation.
*/
func (s *SetStructure) SAdds(key string, members ...string) error {
fs, err := s.checkAndGetSet(key, true)
if err != nil {
return err
}
fs.add(members...)
return s.setSetToDB(stringToBytesWithKey(key), fs)
}

// SRem removes a member from a set
func (s *SetStructure) SRem(key, member string) error {
return s.SRems(key, member)
}

// SRems removes multiple members from a set
func (s *SetStructure) SRems(key string, members ...string) error {
fs, err := s.checkAndGetSet(key, false)
if err != nil {
return err
}
if err = fs.remove(members...); err != nil {
return err
}
return s.setSetToDB(stringToBytesWithKey(key), fs)
}

// SCard gets the cardinality (size) of a set
func (s *SetStructure) SCard(key string) (int, error) {
fs, err := s.checkAndGetSet(key, false)
if err != nil {
return -1, err
}
return len(*fs), nil
}

// SMembers gets all members of a set identified by the provided key.
// If the set does not exist or the key belongs to a different data type,
// the function will return an error.
// It returns a slice of strings which are the members of the set.
func (s *SetStructure) SMembers(key string) ([]string, error) {
fs, err := s.checkAndGetSet(key, false)
if err != nil {
return nil, err
}
// type of fs is map[string]struct{}
// we need to take the keys and make them a slice
var members []string
for k := range *fs {
members = append(members, k)
}

return members, nil
}

// SIsMember checks if a member exists in a set
func (s *SetStructure) SIsMember(key, member string) (bool, error) {
fs, err := s.checkAndGetSet(key, false)
if err != nil {
return false, err
}

return fs.exists(member), nil
}

// SUnion gets the union of multiple sets
func (s *SetStructure) SUnion(keys ...string) ([]string, error) {
// if there are no keys provided, then there's no union
if len(keys) == 0 {
return nil, nil
}
mem := make(map[string]struct{})
var members []string
for _, key := range keys {
fs, err := s.checkAndGetSet(key, false)
if err != nil {
return nil, err
}
for k := range *fs {
if _, ok := mem[k]; !ok {
mem[k] = struct{}{}
members = append(members, k)
}
}
}
return members, nil
}

// SInter returns the intersection of multiple sets.
// The parameter 'keys' is a variadic parameter, meaning
// it can accept any number of arguments. These arguments are the keys
// of the sets that are to be intersected.
func (s *SetStructure) SInter(keys ...string) ([]string, error) {
// if there are no keys, then there's no intersection
if len(keys) == 0 {
return nil, nil
}
// All elements of 'first' are stored in 'mem' map because it's checked
// against each of the subsequent sets.
first, err := s.checkAndGetSet(keys[0], false)
if err != nil {
return nil, err
}

mem := make(map[string]struct{})
for k := range *first {
mem[k] = struct{}{}
}

var members []string
// For each other key, we get its set and its members.
for _, key := range keys[1:] {
fs, err := s.checkAndGetSet(key, false)
if err != nil {
return nil, err
}
// We check if each member of the current set is in 'mem'.
// If yes, we add it to 'members' array, and remove it from 'mem' map.
for k := range *fs {
if _, ok := mem[k]; ok {
delete(mem, k)
members = append(members, k)
}
}
}

return members, nil
}

// SDiff computes the difference between the first and subsequent sets.
// It returns keys unique to the first set and an error if applicable.
func (s *SetStructure) SDiff(keys ...string) ([]string, error) {
// If there are no keys, then there's no difference
if len(keys) == 0 {
return nil, nil
}

first, err := s.checkAndGetSet(keys[0], false)
if err != nil {
return nil, err
}

// Initialize a set for members of first set
mem := make(map[string]struct{})
for k := range *first {
mem[k] = struct{}{}
}

var diffMembers []string
// If member is in the first set and also in another set, remove it.
for _, key := range keys[1:] {
fs, err := s.checkAndGetSet(key, false)
if err != nil {
return nil, err
}
for k := range *fs {
// Only delete the existing members in the first set. Do not add members from other sets
delete(mem, k)
}
}
// Remaining members in mem are unique to first set.
for k := range mem {
diffMembers = append(diffMembers, k)
}

return diffMembers, nil
}

// SUnionStore calculates and stores the union of multiple sets
// in a destination set.
//
// 'destination' is the name of the set where the result will be stored.
//
// 'keys' is a variadic parameter that represents the names of all the sets
// whose union is to be calculated.
//
// The function returns an error if there's any issue computing the union or
// storing the result in the destination set.
//
// Usage example:
//
// set := NewSetStructure(opts) // initialize t
// err := set.SUnionStore("result", "set1", "set2", "set3")
// if err != nil {
// log.Fatal(err)
// }
func (s *SetStructure) SUnionStore(destination string, keys ...string) error {
union, err := s.SUnion(keys...)
if err != nil {
return err
}
return s.SAdds(destination, union...)
}

/*
SInterStore stores the intersection of multiple sets in a destination set.
Function parameters:
- destination (string): The key for the set where the intersection result will be stored.
- keys (string[]): An array of keys for the sets to be intersected.
This function first uses the SInter function to find the intersection of the provided sets (keys).
In case an error occurs during this process (the SInter function returns an error), it will immediately return that error.
Otherwise, it proceeds to use the SAdds function to store the results of the intersection into the destination set.
The function will return the error from the SAdds function if it occurs or nil if the operation is successful.
Errors can occur when there's an issue with accessing the data structures involved in the operations. For instance, trying to perform set operations on non-existing keys or keys that point to non-set data types.
Returns:
- error: An error object that describes an error that occurred during the computation (if any).
*/
func (s *SetStructure) SInterStore(destination string, keys ...string) error {
inter, err := s.SInter(keys...)
if err != nil {
return err
}
return s.SAdds(destination, inter...)
}
func (s *SetStructure) checkAndGetSet(key string, createIfNotExist bool) (*FSets, error) {
// Check if value is empty
if err := checkKey(key); err != nil {
return nil, err
}
keyBytes := stringToBytesWithKey(key)
// Get the list
set, err := s.getSetFromDB(keyBytes, createIfNotExist)
if err != nil {
return nil, err
}

return set, nil
}

// internal/private functions

// add adds new elements to the set data structure (FSets).
// It allows one or more string parameters. If an element already exists, it won't be added again.
func (s *FSets) add(member ...string) {
for _, m := range member {
// Check if the member to be added already exists in the FSets or not.
// existence of the element in the FSets needs to be checked, not the value.
if _, exists := (*s)[m]; !exists {
// If the elements do not already exist, add it to the set.
// The value here is an empty struct, because we are only interested in the keys.
(*s)[m] = struct{}{}
}
}
}

// remove is a method on the FSet struct that takes in a variable number
// of string parameters. It takes these strings to be "members", and generates errors
// if any of these strings are not currently present in the FSet.
//
// Parameters:
//
// member: a variable list of strings that are supposed to be "members" of the FSet.
//
// Returns:
//
// If all of the strings in the members parameter are present in the FSet,
// it then proceeds to remove each of these members from the FSet,
// and returns nil, signifying success.
//
// If any string from the members parameter does not exist in the FSet,
// the function immediately returns an ErrInvalidArgs error,
// which signifies that an invalid argument has been provided to the function.
func (s *FSets) remove(member ...string) error {
if s == nil {
return ErrSetNotInitialized
}
for _, m := range member {
// check to see if all members exist
if _, exists := (*s)[m]; !exists {
return ErrMemberNotFound
}
}
// iterate again
for _, m := range member {
// remove elements from FSets
delete(*s, m)
}
return nil
}

// GetSetFromDB retrieves a set from database given a key. If createIfNotExist is true,
// a new set will be created if the key is not found. It returns the file sets and any write error encountered.
func (s *SetStructure) getSetFromDB(key []byte, createIfNotExist bool) (*FSets, error) {
if s.db == nil {
return nil, ErrSetNotInitialized
}
dbData, err := s.db.Get(key)
var zSetValue FSets
// If key is not found, return nil for both; otherwise return the error.
if err != nil {
if errors.Is(err, _const.ErrKeyNotFound) && createIfNotExist {
return &FSets{}, nil
}
return nil, err
} else {
err = encoding.NewMessagePackDecoder(dbData).Decode(&zSetValue)
if err != nil {
return nil, err
}
return &zSetValue, err
}
// return a pointer to the deserialized ZSetNodes, nil for the error
}

// setSetToDB
func (s *SetStructure) setSetToDB(key []byte, zSetValue *FSets) error {
val := encoding.NewMessagePackEncoder()
err := val.Encode(zSetValue)
if err != nil {
return err
}
return s.db.Put(key, val.Bytes())
}

func (s *SetStructure) exists(key string, member ...string) bool {
if err := checkKey(key); err != nil {
return false
}
keyBytes := stringToBytesWithKey(key)

zSet, err := s.getSetFromDB(keyBytes, false)

if err != nil {
return false
}
return zSet.exists(member...)
}
func (s *FSets) exists(member ...string) bool {
for _, s2 := range member {
if _, ok := (*s)[s2]; !ok {
return false
}
}
return true
}
Loading

0 comments on commit 3f06b04

Please sign in to comment.