mirror of
https://github.com/nabbar/golib.git
synced 2025-10-18 05:41:55 +08:00
Add package NutsDB :
- use lib nutsdb (https://github.com/xujiajun/nutsdb) - implement client to use embedded db cluster - convert nutsdb lib as clustered nutsdb with lib cluster (based on dragonboat) - implement disk backend lib for dragonboat - add complete config with nutsdb, node & cluster with validator
This commit is contained in:
232
nutsdb/Command.go
Normal file
232
nutsdb/Command.go
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/xujiajun/nutsdb"
|
||||||
|
"github.com/xujiajun/nutsdb/ds/zset"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Commands interface {
|
||||||
|
CommandTransaction
|
||||||
|
CommandBPTree
|
||||||
|
CommandSet
|
||||||
|
CommandList
|
||||||
|
CommandZSet
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandTransaction interface {
|
||||||
|
// Put sets the value for a key in the bucket.
|
||||||
|
Put(bucket string, key, value []byte, ttl uint32) error
|
||||||
|
|
||||||
|
// PutWithTimestamp sets the value for a key in the bucket but allow capabilities to custom the timestamp for ttl.
|
||||||
|
PutWithTimestamp(bucket string, key, value []byte, ttl uint32, timestamp uint64) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandBPTree interface {
|
||||||
|
// Get retrieves the value for a key in the bucket.
|
||||||
|
// The returned value is only valid for the life of the transaction.
|
||||||
|
Get(bucket string, key []byte) (e *nutsdb.Entry, err error)
|
||||||
|
|
||||||
|
//GetAll returns all keys and values of the bucket stored at given bucket.
|
||||||
|
GetAll(bucket string) (entries nutsdb.Entries, err error)
|
||||||
|
|
||||||
|
// RangeScan query a range at given bucket, start and end slice.
|
||||||
|
RangeScan(bucket string, start, end []byte) (es nutsdb.Entries, err error)
|
||||||
|
|
||||||
|
// PrefixScan iterates over a key prefix at given bucket, prefix and limitNum.
|
||||||
|
// LimitNum will limit the number of entries return.
|
||||||
|
PrefixScan(bucket string, prefix []byte, offsetNum int, limitNum int) (es nutsdb.Entries, off int, err error)
|
||||||
|
|
||||||
|
// PrefixSearchScan iterates over a key prefix at given bucket, prefix, match regular expression and limitNum.
|
||||||
|
// LimitNum will limit the number of entries return.
|
||||||
|
PrefixSearchScan(bucket string, prefix []byte, reg string, offsetNum int, limitNum int) (es nutsdb.Entries, off int, err error)
|
||||||
|
|
||||||
|
// Delete removes a key from the bucket at given bucket and key.
|
||||||
|
Delete(bucket string, key []byte) error
|
||||||
|
|
||||||
|
// FindTxIDOnDisk returns if txId on disk at given fid and txID.
|
||||||
|
FindTxIDOnDisk(fID, txID uint64) (ok bool, err error)
|
||||||
|
|
||||||
|
// FindOnDisk returns entry on disk at given fID, rootOff and key.
|
||||||
|
FindOnDisk(fID uint64, rootOff uint64, key, newKey []byte) (entry *nutsdb.Entry, err error)
|
||||||
|
|
||||||
|
// FindLeafOnDisk returns binary leaf node on disk at given fId, rootOff and key.
|
||||||
|
FindLeafOnDisk(fID int64, rootOff int64, key, newKey []byte) (bn *nutsdb.BinaryNode, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandSet interface {
|
||||||
|
// SAdd adds the specified members to the set stored int the bucket at given bucket,key and items.
|
||||||
|
SAdd(bucket string, key []byte, items ...[]byte) error
|
||||||
|
|
||||||
|
// SRem removes the specified members from the set stored int the bucket at given bucket,key and items.
|
||||||
|
SRem(bucket string, key []byte, items ...[]byte) error
|
||||||
|
|
||||||
|
// SAreMembers returns if the specified members are the member of the set int the bucket at given bucket,key and items.
|
||||||
|
SAreMembers(bucket string, key []byte, items ...[]byte) (bool, error)
|
||||||
|
|
||||||
|
// SIsMember returns if member is a member of the set stored int the bucket at given bucket,key and item.
|
||||||
|
SIsMember(bucket string, key, item []byte) (bool, error)
|
||||||
|
|
||||||
|
// SMembers returns all the members of the set value stored int the bucket at given bucket and key.
|
||||||
|
SMembers(bucket string, key []byte) (list [][]byte, err error)
|
||||||
|
|
||||||
|
// SHasKey returns if the set in the bucket at given bucket and key.
|
||||||
|
SHasKey(bucket string, key []byte) (bool, error)
|
||||||
|
|
||||||
|
// SPop removes and returns one or more random elements from the set value store in the bucket at given bucket and key.
|
||||||
|
SPop(bucket string, key []byte) ([]byte, error)
|
||||||
|
|
||||||
|
// SCard returns the set cardinality (number of elements) of the set stored in the bucket at given bucket and key.
|
||||||
|
SCard(bucket string, key []byte) (int, error)
|
||||||
|
|
||||||
|
// SDiffByOneBucket returns the members of the set resulting from the difference
|
||||||
|
// between the first set and all the successive sets in one bucket.
|
||||||
|
SDiffByOneBucket(bucket string, key1, key2 []byte) (list [][]byte, err error)
|
||||||
|
|
||||||
|
// SDiffByTwoBuckets returns the members of the set resulting from the difference
|
||||||
|
// between the first set and all the successive sets in two buckets.
|
||||||
|
SDiffByTwoBuckets(bucket1 string, key1 []byte, bucket2 string, key2 []byte) (list [][]byte, err error)
|
||||||
|
|
||||||
|
// SMoveByOneBucket moves member from the set at source to the set at destination in one bucket.
|
||||||
|
SMoveByOneBucket(bucket string, key1, key2, item []byte) (bool, error)
|
||||||
|
|
||||||
|
// SMoveByTwoBuckets moves member from the set at source to the set at destination in two buckets.
|
||||||
|
SMoveByTwoBuckets(bucket1 string, key1 []byte, bucket2 string, key2, item []byte) (bool, error)
|
||||||
|
|
||||||
|
// SUnionByOneBucket the members of the set resulting from the union of all the given sets in one bucket.
|
||||||
|
SUnionByOneBucket(bucket string, key1, key2 []byte) (list [][]byte, err error)
|
||||||
|
|
||||||
|
// SUnionByTwoBuckets the members of the set resulting from the union of all the given sets in two buckets.
|
||||||
|
SUnionByTwoBuckets(bucket1 string, key1 []byte, bucket2 string, key2 []byte) (list [][]byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandList interface {
|
||||||
|
// RPop removes and returns the last element of the list stored in the bucket at given bucket and key.
|
||||||
|
RPop(bucket string, key []byte) (item []byte, err error)
|
||||||
|
|
||||||
|
// RPeek returns the last element of the list stored in the bucket at given bucket and key.
|
||||||
|
RPeek(bucket string, key []byte) (item []byte, err error)
|
||||||
|
|
||||||
|
// RPush inserts the values at the tail of the list stored in the bucket at given bucket,key and values.
|
||||||
|
RPush(bucket string, key []byte, values ...[]byte) error
|
||||||
|
|
||||||
|
// LPush inserts the values at the head of the list stored in the bucket at given bucket,key and values.
|
||||||
|
LPush(bucket string, key []byte, values ...[]byte) error
|
||||||
|
|
||||||
|
// LPop removes and returns the first element of the list stored in the bucket at given bucket and key.
|
||||||
|
LPop(bucket string, key []byte) (item []byte, err error)
|
||||||
|
|
||||||
|
// LPeek returns the first element of the list stored in the bucket at given bucket and key.
|
||||||
|
LPeek(bucket string, key []byte) (item []byte, err error)
|
||||||
|
|
||||||
|
// LSize returns the size of key in the bucket in the bucket at given bucket and key.
|
||||||
|
LSize(bucket string, key []byte) (int, error)
|
||||||
|
|
||||||
|
// LRange returns the specified elements of the list stored in the bucket at given bucket,key, start and end.
|
||||||
|
// The offsets start and stop are zero-based indexes 0 being the first element of the list (the head of the list),
|
||||||
|
// 1 being the next element and so on.
|
||||||
|
// Start and end can also be negative numbers indicating offsets from the end of the list,
|
||||||
|
// where -1 is the last element of the list, -2 the penultimate element and so on.
|
||||||
|
LRange(bucket string, key []byte, start, end int) (list [][]byte, err error)
|
||||||
|
|
||||||
|
// LRem removes the first count occurrences of elements equal to value from the list stored in the bucket at given bucket,key,count.
|
||||||
|
// The count argument influences the operation in the following ways:
|
||||||
|
// count > 0: Remove elements equal to value moving from head to tail.
|
||||||
|
// count < 0: Remove elements equal to value moving from tail to head.
|
||||||
|
// count = 0: Remove all elements equal to value.
|
||||||
|
LRem(bucket string, key []byte, count int, value []byte) (removedNum int, err error)
|
||||||
|
|
||||||
|
// LSet sets the list element at index to value.
|
||||||
|
LSet(bucket string, key []byte, index int, value []byte) error
|
||||||
|
|
||||||
|
// LTrim trims an existing list so that it will contain only the specified range of elements specified.
|
||||||
|
// the offsets start and stop are zero-based indexes 0 being the first element of the list (the head of the list),
|
||||||
|
// 1 being the next element and so on.
|
||||||
|
// start and end can also be negative numbers indicating offsets from the end of the list,
|
||||||
|
// where -1 is the last element of the list, -2 the penultimate element and so on.
|
||||||
|
LTrim(bucket string, key []byte, start, end int) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandZSet interface {
|
||||||
|
// ZAdd adds the specified member key with the specified score and specified val to the sorted set stored at bucket.
|
||||||
|
ZAdd(bucket string, key []byte, score float64, val []byte) error
|
||||||
|
|
||||||
|
// ZMembers returns all the members of the set value stored at bucket.
|
||||||
|
ZMembers(bucket string) (map[string]*zset.SortedSetNode, error)
|
||||||
|
|
||||||
|
// ZCard returns the sorted set cardinality (number of elements) of the sorted set stored at bucket.
|
||||||
|
ZCard(bucket string) (int, error)
|
||||||
|
|
||||||
|
// ZCount returns the number of elements in the sorted set at bucket with a score between min and max and opts.
|
||||||
|
// opts includes the following parameters:
|
||||||
|
// Limit int // limit the max nodes to return
|
||||||
|
// ExcludeStart bool // exclude start value, so it search in interval (start, end] or (start, end)
|
||||||
|
// ExcludeEnd bool // exclude end value, so it search in interval [start, end) or (start, end)
|
||||||
|
ZCount(bucket string, start, end float64, opts *zset.GetByScoreRangeOptions) (int, error)
|
||||||
|
|
||||||
|
// ZPopMax removes and returns the member with the highest score in the sorted set stored at bucket.
|
||||||
|
ZPopMax(bucket string) (*zset.SortedSetNode, error)
|
||||||
|
|
||||||
|
// ZPopMin removes and returns the member with the lowest score in the sorted set stored at bucket.
|
||||||
|
ZPopMin(bucket string) (*zset.SortedSetNode, error)
|
||||||
|
|
||||||
|
// ZPeekMax returns the member with the highest score in the sorted set stored at bucket.
|
||||||
|
ZPeekMax(bucket string) (*zset.SortedSetNode, error)
|
||||||
|
|
||||||
|
// ZPeekMin returns the member with the lowest score in the sorted set stored at bucket.
|
||||||
|
ZPeekMin(bucket string) (*zset.SortedSetNode, error)
|
||||||
|
|
||||||
|
// ZRangeByScore returns all the elements in the sorted set at bucket with a score between min and max.
|
||||||
|
ZRangeByScore(bucket string, start, end float64, opts *zset.GetByScoreRangeOptions) ([]*zset.SortedSetNode, error)
|
||||||
|
|
||||||
|
// ZRangeByRank returns all the elements in the sorted set in one bucket and key
|
||||||
|
// with a rank between start and end (including elements with rank equal to start or end).
|
||||||
|
ZRangeByRank(bucket string, start, end int) ([]*zset.SortedSetNode, error)
|
||||||
|
|
||||||
|
// ZRem removes the specified members from the sorted set stored in one bucket at given bucket and key.
|
||||||
|
ZRem(bucket, key string) error
|
||||||
|
|
||||||
|
// ZRemRangeByRank removes all elements in the sorted set stored in one bucket at given bucket with rank between start and end.
|
||||||
|
// the rank is 1-based integer. Rank 1 means the first node; Rank -1 means the last node.
|
||||||
|
ZRemRangeByRank(bucket string, start, end int) error
|
||||||
|
|
||||||
|
// ZRank returns the rank of member in the sorted set stored in the bucket at given bucket and key,
|
||||||
|
// with the scores ordered from low to high.
|
||||||
|
ZRank(bucket string, key []byte) (int, error)
|
||||||
|
|
||||||
|
// ZRevRank returns the rank of member in the sorted set stored in the bucket at given bucket and key,
|
||||||
|
// with the scores ordered from high to low.
|
||||||
|
ZRevRank(bucket string, key []byte) (int, error)
|
||||||
|
|
||||||
|
// ZScore returns the score of member in the sorted set in the bucket at given bucket and key.
|
||||||
|
ZScore(bucket string, key []byte) (float64, error)
|
||||||
|
|
||||||
|
// ZGetByKey returns node in the bucket at given bucket and key.
|
||||||
|
ZGetByKey(bucket string, key []byte) (*zset.SortedSetNode, error)
|
||||||
|
}
|
1464
nutsdb/client.go
Normal file
1464
nutsdb/client.go
Normal file
File diff suppressed because it is too large
Load Diff
420
nutsdb/cmdCode.go
Normal file
420
nutsdb/cmdCode.go
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
type CmdCode uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CmdUnknown is no Command.
|
||||||
|
CmdUnknown CmdCode = iota
|
||||||
|
// Command for transaction.
|
||||||
|
CmdPut
|
||||||
|
CmdPutWithTimestamp
|
||||||
|
// Command for BPTree.
|
||||||
|
CmdGet
|
||||||
|
CmdGetAll
|
||||||
|
CmdRangeScan
|
||||||
|
CmdPrefixScan
|
||||||
|
CmdPrefixSearchScan
|
||||||
|
CmdDelete
|
||||||
|
CmdFindTxIDOnDisk
|
||||||
|
CmdFindOnDisk
|
||||||
|
CmdFindLeafOnDisk
|
||||||
|
// Command for Set.
|
||||||
|
CmdSAdd
|
||||||
|
CmdSRem
|
||||||
|
CmdSAreMembers
|
||||||
|
CmdSIsMember
|
||||||
|
CmdSMembers
|
||||||
|
CmdSHasKey
|
||||||
|
CmdSPop
|
||||||
|
CmdSCard
|
||||||
|
CmdSDiffByOneBucket
|
||||||
|
CmdSDiffByTwoBuckets
|
||||||
|
CmdSMoveByOneBucket
|
||||||
|
CmdSMoveByTwoBuckets
|
||||||
|
CmdSUnionByOneBucket
|
||||||
|
CmdSUnionByTwoBuckets
|
||||||
|
// Command for List.
|
||||||
|
CmdRPop
|
||||||
|
CmdRPeek
|
||||||
|
CmdRPush
|
||||||
|
CmdLPush
|
||||||
|
CmdLPop
|
||||||
|
CmdLPeek
|
||||||
|
CmdLSize
|
||||||
|
CmdLRange
|
||||||
|
CmdLRem
|
||||||
|
CmdLSet
|
||||||
|
CmdLTrim
|
||||||
|
// Command for ZSet.
|
||||||
|
CmdZAdd
|
||||||
|
CmdZMembers
|
||||||
|
CmdZCard
|
||||||
|
CmdZCount
|
||||||
|
CmdZPopMax
|
||||||
|
CmdZPopMin
|
||||||
|
CmdZPeekMax
|
||||||
|
CmdZPeekMin
|
||||||
|
CmdZRangeByScore
|
||||||
|
CmdZRangeByRank
|
||||||
|
CmdZRem
|
||||||
|
CmdZRemRangeByRank
|
||||||
|
CmdZRank
|
||||||
|
CmdZRevRank
|
||||||
|
CmdZScore
|
||||||
|
CmdZGetByKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// nolint #funlen
|
||||||
|
func CmdCodeFromName(name string) CmdCode {
|
||||||
|
switch name {
|
||||||
|
case CmdPut.Name():
|
||||||
|
return CmdPut
|
||||||
|
|
||||||
|
case CmdPutWithTimestamp.Name():
|
||||||
|
return CmdPutWithTimestamp
|
||||||
|
|
||||||
|
case CmdGet.Name():
|
||||||
|
return CmdGet
|
||||||
|
|
||||||
|
case CmdGetAll.Name():
|
||||||
|
return CmdGetAll
|
||||||
|
|
||||||
|
case CmdRangeScan.Name():
|
||||||
|
return CmdRangeScan
|
||||||
|
|
||||||
|
case CmdPrefixScan.Name():
|
||||||
|
return CmdPrefixScan
|
||||||
|
|
||||||
|
case CmdPrefixSearchScan.Name():
|
||||||
|
return CmdPrefixSearchScan
|
||||||
|
|
||||||
|
case CmdDelete.Name():
|
||||||
|
return CmdDelete
|
||||||
|
|
||||||
|
case CmdFindTxIDOnDisk.Name():
|
||||||
|
return CmdFindTxIDOnDisk
|
||||||
|
|
||||||
|
case CmdFindOnDisk.Name():
|
||||||
|
return CmdFindOnDisk
|
||||||
|
|
||||||
|
case CmdFindLeafOnDisk.Name():
|
||||||
|
return CmdFindLeafOnDisk
|
||||||
|
|
||||||
|
case CmdSAdd.Name():
|
||||||
|
return CmdSAdd
|
||||||
|
|
||||||
|
case CmdSRem.Name():
|
||||||
|
return CmdSRem
|
||||||
|
|
||||||
|
case CmdSAreMembers.Name():
|
||||||
|
return CmdSAreMembers
|
||||||
|
|
||||||
|
case CmdSIsMember.Name():
|
||||||
|
return CmdSIsMember
|
||||||
|
|
||||||
|
case CmdSMembers.Name():
|
||||||
|
return CmdSMembers
|
||||||
|
|
||||||
|
case CmdSHasKey.Name():
|
||||||
|
return CmdSHasKey
|
||||||
|
|
||||||
|
case CmdSPop.Name():
|
||||||
|
return CmdSPop
|
||||||
|
|
||||||
|
case CmdSCard.Name():
|
||||||
|
return CmdSCard
|
||||||
|
|
||||||
|
case CmdSDiffByOneBucket.Name():
|
||||||
|
return CmdSDiffByOneBucket
|
||||||
|
|
||||||
|
case CmdSDiffByTwoBuckets.Name():
|
||||||
|
return CmdSDiffByTwoBuckets
|
||||||
|
|
||||||
|
case CmdSMoveByOneBucket.Name():
|
||||||
|
return CmdSMoveByOneBucket
|
||||||
|
|
||||||
|
case CmdSMoveByTwoBuckets.Name():
|
||||||
|
return CmdSMoveByTwoBuckets
|
||||||
|
|
||||||
|
case CmdSUnionByOneBucket.Name():
|
||||||
|
return CmdSUnionByOneBucket
|
||||||
|
|
||||||
|
case CmdSUnionByTwoBuckets.Name():
|
||||||
|
return CmdSUnionByTwoBuckets
|
||||||
|
|
||||||
|
case CmdRPop.Name():
|
||||||
|
return CmdRPop
|
||||||
|
|
||||||
|
case CmdRPeek.Name():
|
||||||
|
return CmdRPeek
|
||||||
|
|
||||||
|
case CmdRPush.Name():
|
||||||
|
return CmdRPush
|
||||||
|
|
||||||
|
case CmdLPush.Name():
|
||||||
|
return CmdLPush
|
||||||
|
|
||||||
|
case CmdLPop.Name():
|
||||||
|
return CmdLPop
|
||||||
|
|
||||||
|
case CmdLPeek.Name():
|
||||||
|
return CmdLPeek
|
||||||
|
|
||||||
|
case CmdLSize.Name():
|
||||||
|
return CmdLSize
|
||||||
|
|
||||||
|
case CmdLRange.Name():
|
||||||
|
return CmdLRange
|
||||||
|
|
||||||
|
case CmdLRem.Name():
|
||||||
|
return CmdLRem
|
||||||
|
|
||||||
|
case CmdLSet.Name():
|
||||||
|
return CmdLSet
|
||||||
|
|
||||||
|
case CmdLTrim.Name():
|
||||||
|
return CmdLTrim
|
||||||
|
|
||||||
|
case CmdZAdd.Name():
|
||||||
|
return CmdZAdd
|
||||||
|
|
||||||
|
case CmdZMembers.Name():
|
||||||
|
return CmdZMembers
|
||||||
|
|
||||||
|
case CmdZCard.Name():
|
||||||
|
return CmdZCard
|
||||||
|
|
||||||
|
case CmdZCount.Name():
|
||||||
|
return CmdZCount
|
||||||
|
|
||||||
|
case CmdZPopMax.Name():
|
||||||
|
return CmdZPopMax
|
||||||
|
|
||||||
|
case CmdZPopMin.Name():
|
||||||
|
return CmdZPopMin
|
||||||
|
|
||||||
|
case CmdZPeekMax.Name():
|
||||||
|
return CmdZPeekMax
|
||||||
|
|
||||||
|
case CmdZPeekMin.Name():
|
||||||
|
return CmdZPeekMin
|
||||||
|
|
||||||
|
case CmdZRangeByScore.Name():
|
||||||
|
return CmdZRangeByScore
|
||||||
|
|
||||||
|
case CmdZRangeByRank.Name():
|
||||||
|
return CmdZRangeByRank
|
||||||
|
|
||||||
|
case CmdZRem.Name():
|
||||||
|
return CmdZRem
|
||||||
|
|
||||||
|
case CmdZRemRangeByRank.Name():
|
||||||
|
return CmdZRemRangeByRank
|
||||||
|
|
||||||
|
case CmdZRank.Name():
|
||||||
|
return CmdZRank
|
||||||
|
|
||||||
|
case CmdZRevRank.Name():
|
||||||
|
return CmdZRevRank
|
||||||
|
|
||||||
|
case CmdZScore.Name():
|
||||||
|
return CmdZScore
|
||||||
|
|
||||||
|
case CmdZGetByKey.Name():
|
||||||
|
return CmdZGetByKey
|
||||||
|
|
||||||
|
default:
|
||||||
|
return CmdUnknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nolint #funlen
|
||||||
|
func (c CmdCode) Name() string {
|
||||||
|
switch c {
|
||||||
|
case CmdPut:
|
||||||
|
return "Put"
|
||||||
|
|
||||||
|
case CmdPutWithTimestamp:
|
||||||
|
return "PutWithTimestamp"
|
||||||
|
|
||||||
|
case CmdGet:
|
||||||
|
return "Get"
|
||||||
|
|
||||||
|
case CmdGetAll:
|
||||||
|
return "GetAll"
|
||||||
|
|
||||||
|
case CmdRangeScan:
|
||||||
|
return "RangeScan"
|
||||||
|
|
||||||
|
case CmdPrefixScan:
|
||||||
|
return "PrefixScan"
|
||||||
|
|
||||||
|
case CmdPrefixSearchScan:
|
||||||
|
return "PrefixSearchScan"
|
||||||
|
|
||||||
|
case CmdDelete:
|
||||||
|
return "Delete"
|
||||||
|
|
||||||
|
case CmdFindTxIDOnDisk:
|
||||||
|
return "FindTxIDOnDisk"
|
||||||
|
|
||||||
|
case CmdFindOnDisk:
|
||||||
|
return "FindOnDisk"
|
||||||
|
|
||||||
|
case CmdFindLeafOnDisk:
|
||||||
|
return "FindLeafOnDisk"
|
||||||
|
|
||||||
|
case CmdSAdd:
|
||||||
|
return "SAdd"
|
||||||
|
|
||||||
|
case CmdSRem:
|
||||||
|
return "SRem"
|
||||||
|
|
||||||
|
case CmdSAreMembers:
|
||||||
|
return "SAreMembers"
|
||||||
|
|
||||||
|
case CmdSIsMember:
|
||||||
|
return "SIsMember"
|
||||||
|
|
||||||
|
case CmdSMembers:
|
||||||
|
return "SMembers"
|
||||||
|
|
||||||
|
case CmdSHasKey:
|
||||||
|
return "SHasKey"
|
||||||
|
|
||||||
|
case CmdSPop:
|
||||||
|
return "SPop"
|
||||||
|
|
||||||
|
case CmdSCard:
|
||||||
|
return "SCard"
|
||||||
|
|
||||||
|
case CmdSDiffByOneBucket:
|
||||||
|
return "SDiffByOneBucket"
|
||||||
|
|
||||||
|
case CmdSDiffByTwoBuckets:
|
||||||
|
return "SDiffByTwoBuckets"
|
||||||
|
|
||||||
|
case CmdSMoveByOneBucket:
|
||||||
|
return "SMoveByOneBucket"
|
||||||
|
|
||||||
|
case CmdSMoveByTwoBuckets:
|
||||||
|
return "SMoveByTwoBuckets"
|
||||||
|
|
||||||
|
case CmdSUnionByOneBucket:
|
||||||
|
return "SUnionByOneBucket"
|
||||||
|
|
||||||
|
case CmdSUnionByTwoBuckets:
|
||||||
|
return "SUnionByTwoBuckets"
|
||||||
|
|
||||||
|
case CmdRPop:
|
||||||
|
return "RPop"
|
||||||
|
|
||||||
|
case CmdRPeek:
|
||||||
|
return "RPeek"
|
||||||
|
|
||||||
|
case CmdRPush:
|
||||||
|
return "RPush"
|
||||||
|
|
||||||
|
case CmdLPush:
|
||||||
|
return "LPush"
|
||||||
|
|
||||||
|
case CmdLPop:
|
||||||
|
return "LPop"
|
||||||
|
|
||||||
|
case CmdLPeek:
|
||||||
|
return "LPeek"
|
||||||
|
|
||||||
|
case CmdLSize:
|
||||||
|
return "LSize"
|
||||||
|
|
||||||
|
case CmdLRange:
|
||||||
|
return "LRange"
|
||||||
|
|
||||||
|
case CmdLRem:
|
||||||
|
return "LRem"
|
||||||
|
|
||||||
|
case CmdLSet:
|
||||||
|
return "LSet"
|
||||||
|
|
||||||
|
case CmdLTrim:
|
||||||
|
return "LTrim"
|
||||||
|
|
||||||
|
case CmdZAdd:
|
||||||
|
return "ZAdd"
|
||||||
|
|
||||||
|
case CmdZMembers:
|
||||||
|
return "ZMembers"
|
||||||
|
|
||||||
|
case CmdZCard:
|
||||||
|
return "ZCard"
|
||||||
|
|
||||||
|
case CmdZCount:
|
||||||
|
return "ZCount"
|
||||||
|
|
||||||
|
case CmdZPopMax:
|
||||||
|
return "ZPopMax"
|
||||||
|
|
||||||
|
case CmdZPopMin:
|
||||||
|
return "ZPopMin"
|
||||||
|
|
||||||
|
case CmdZPeekMax:
|
||||||
|
return "ZPeekMax"
|
||||||
|
|
||||||
|
case CmdZPeekMin:
|
||||||
|
return "ZPeekMin"
|
||||||
|
|
||||||
|
case CmdZRangeByScore:
|
||||||
|
return "ZRangeByScore"
|
||||||
|
|
||||||
|
case CmdZRangeByRank:
|
||||||
|
return "ZRangeByRank"
|
||||||
|
|
||||||
|
case CmdZRem:
|
||||||
|
return "ZRem"
|
||||||
|
|
||||||
|
case CmdZRemRangeByRank:
|
||||||
|
return "ZRemRangeByRank"
|
||||||
|
|
||||||
|
case CmdZRank:
|
||||||
|
return "ZRank"
|
||||||
|
|
||||||
|
case CmdZRevRank:
|
||||||
|
return "ZRevRank"
|
||||||
|
|
||||||
|
case CmdZScore:
|
||||||
|
return "ZScore"
|
||||||
|
|
||||||
|
case CmdZGetByKey:
|
||||||
|
return "ZGetByKey"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
107
nutsdb/config.go
Normal file
107
nutsdb/config.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/nabbar/golib/cluster"
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
"github.com/xujiajun/nutsdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
DB NutsDBOptions `mapstructure:"db" json:"db" yaml:"db" toml:"db"`
|
||||||
|
Cluster cluster.Config `mapstructure:"cluster" json:"cluster" yaml:"cluster" toml:"cluster"`
|
||||||
|
Directory NutsDBFolder `mapstructure:"directories" json:"directories" yaml:"directories" toml:"directories" `
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) GetConfigFolder() NutsDBFolder {
|
||||||
|
return c.Directory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) GetConfigDB() (nutsdb.Options, liberr.Error) {
|
||||||
|
if dir, err := c.Directory.GetDirectoryData(); err != nil {
|
||||||
|
return nutsdb.Options{}, err
|
||||||
|
} else {
|
||||||
|
return c.DB.GetNutsDBOptions(dir), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) GetConfigCluster() (cluster.Config, liberr.Error) {
|
||||||
|
cfg := c.Cluster
|
||||||
|
|
||||||
|
if dir, err := c.Directory.GetDirectoryWal(); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
} else {
|
||||||
|
cfg.Node.WALDir = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir, err := c.Directory.GetDirectoryHost(); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
} else {
|
||||||
|
cfg.Node.NodeHostDir = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) GetOptions() (Options, liberr.Error) {
|
||||||
|
return NewOptions(c.DB, c.Directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) Validate() liberr.Error {
|
||||||
|
val := validator.New()
|
||||||
|
err := val.Struct(c)
|
||||||
|
|
||||||
|
if e, ok := err.(*validator.InvalidValidationError); ok {
|
||||||
|
return ErrorValidateConfig.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := ErrorValidateConfig.Error(nil)
|
||||||
|
|
||||||
|
for _, e := range err.(validator.ValidationErrors) {
|
||||||
|
//nolint goerr113
|
||||||
|
out.AddParent(fmt.Errorf("config field '%s' is not validated by constraint '%s'", e.Field(), e.ActualTag()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.HasParent() {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) ValidateDB() liberr.Error {
|
||||||
|
return c.DB.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) ValidateCluster() liberr.Error {
|
||||||
|
return c.Cluster.Validate()
|
||||||
|
}
|
126
nutsdb/configDb.go
Normal file
126
nutsdb/configDb.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
"github.com/xujiajun/nutsdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NutsDBOptions struct {
|
||||||
|
// EntryIdxMode represents using which mode to index the entries.
|
||||||
|
EntryIdxMode nutsdb.EntryIdxMode `mapstructure:"entry_idx_mode" json:"entry_idx_mode" yaml:"entry_idx_mode" toml:"entry_idx_mode"`
|
||||||
|
|
||||||
|
// RWMode represents the read and write mode.
|
||||||
|
// RWMode includes two options: FileIO and MMap.
|
||||||
|
// FileIO represents the read and write mode using standard I/O.
|
||||||
|
// MMap represents the read and write mode using mmap.
|
||||||
|
RWMode nutsdb.RWMode `mapstructure:"rw_mode" json:"rw_mode" yaml:"rw_mode" toml:"rw_mode"`
|
||||||
|
|
||||||
|
// SegmentSize default value is 8 MBytes
|
||||||
|
SegmentSize int64 `mapstructure:"segment_size" json:"segment_size" yaml:"segment_size" toml:"segment_size"`
|
||||||
|
|
||||||
|
// SyncEnable represents if call Sync() function.
|
||||||
|
// if SyncEnable is false, high write performance but potential data loss likely.
|
||||||
|
// if SyncEnable is true, slower but persistent.
|
||||||
|
SyncEnable bool `mapstructure:"sync_enable" json:"sync_enable" yaml:"sync_enable" toml:"sync_enable"`
|
||||||
|
|
||||||
|
// StartFileLoadingMode represents when open a database which RWMode to load files.
|
||||||
|
StartFileLoadingMode nutsdb.RWMode `mapstructure:"start_file_loading_mode" json:"start_file_loading_mode" yaml:"start_file_loading_mode" toml:"start_file_loading_mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o NutsDBOptions) GetNutsDBOptions(dataDir string) nutsdb.Options {
|
||||||
|
d := nutsdb.DefaultOptions
|
||||||
|
|
||||||
|
if len(dataDir) < 1 {
|
||||||
|
d.RWMode = nutsdb.MMap
|
||||||
|
d.StartFileLoadingMode = nutsdb.MMap
|
||||||
|
} else {
|
||||||
|
d.Dir = dataDir
|
||||||
|
|
||||||
|
switch o.RWMode {
|
||||||
|
case nutsdb.MMap:
|
||||||
|
d.RWMode = nutsdb.MMap
|
||||||
|
default:
|
||||||
|
d.RWMode = nutsdb.FileIO
|
||||||
|
}
|
||||||
|
|
||||||
|
switch o.StartFileLoadingMode {
|
||||||
|
case nutsdb.MMap:
|
||||||
|
d.StartFileLoadingMode = nutsdb.MMap
|
||||||
|
default:
|
||||||
|
d.StartFileLoadingMode = nutsdb.FileIO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch o.EntryIdxMode {
|
||||||
|
case nutsdb.HintKeyAndRAMIdxMode:
|
||||||
|
d.EntryIdxMode = nutsdb.HintKeyAndRAMIdxMode
|
||||||
|
case nutsdb.HintBPTSparseIdxMode:
|
||||||
|
d.EntryIdxMode = nutsdb.HintBPTSparseIdxMode
|
||||||
|
default:
|
||||||
|
d.EntryIdxMode = nutsdb.HintKeyValAndRAMIdxMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.SegmentSize > 0 {
|
||||||
|
d.SegmentSize = o.SegmentSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.SyncEnable {
|
||||||
|
d.SyncEnable = true
|
||||||
|
} else {
|
||||||
|
d.SyncEnable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o NutsDBOptions) Validate() liberr.Error {
|
||||||
|
val := validator.New()
|
||||||
|
err := val.Struct(o)
|
||||||
|
|
||||||
|
if e, ok := err.(*validator.InvalidValidationError); ok {
|
||||||
|
return ErrorValidateConfig.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := ErrorValidateConfig.Error(nil)
|
||||||
|
|
||||||
|
for _, e := range err.(validator.ValidationErrors) {
|
||||||
|
//nolint goerr113
|
||||||
|
out.AddParent(fmt.Errorf("config field '%s' is not validated by constraint '%s'", e.Field(), e.ActualTag()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.HasParent() {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
196
nutsdb/configFs.go
Normal file
196
nutsdb/configFs.go
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_DefaultFolderData = "data"
|
||||||
|
_DefaultFolderBackup = "backup"
|
||||||
|
_DefaultFolderWal = "wal"
|
||||||
|
_DefaultFolderHost = "host"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NutsDBFolder struct {
|
||||||
|
// Working represents the main working folder witch will include sub directories : data, backup, temp...
|
||||||
|
// If the base directory is empty, all the sub directory will be absolute directories.
|
||||||
|
Base string `mapstructure:"base" json:"base" yaml:"base" toml:"base"`
|
||||||
|
|
||||||
|
// Data represents the sub-dir for the opening database.
|
||||||
|
// By default, it will use `data` as sub folder
|
||||||
|
Data string `mapstructure:"sub_data" json:"sub_data" yaml:"sub_data" toml:"sub_data"`
|
||||||
|
|
||||||
|
// Backup represents the sub-dir with all backup sub-folder.
|
||||||
|
// By default, it will use `backup` as sub folder
|
||||||
|
Backup string `mapstructure:"sub_backup" json:"sub_backup" yaml:"sub_backup" toml:"sub_backup"`
|
||||||
|
|
||||||
|
// Temp represents the sub-dir for cluster negotiation.
|
||||||
|
// By default, it will use the system temporary folder
|
||||||
|
Temp string `mapstructure:"sub_temp" json:"sub_temp" yaml:"sub_temp" toml:"sub_temp"`
|
||||||
|
|
||||||
|
// Temp represents the sub-dir for cluster negotiation.
|
||||||
|
// By default, it will use the system temporary folder
|
||||||
|
WalDir string `mapstructure:"wal_dir" json:"wal_dir" yaml:"wal_dir" toml:"wal_dir"`
|
||||||
|
|
||||||
|
// Temp represents the sub-dir for cluster negotiation.
|
||||||
|
// By default, it will use the system temporary folder
|
||||||
|
HostDir string `mapstructure:"host_dir" json:"host_dir" yaml:"host_dir" toml:"host_dir"`
|
||||||
|
|
||||||
|
// LimitNumberBackup represents how many backup will be keep.
|
||||||
|
LimitNumberBackup uint8 `mapstructure:"limit_number_backup" json:"limit_number_backup" yaml:"limit_number_backup" toml:"limit_number_backup"`
|
||||||
|
|
||||||
|
// Permission represents the perission apply to folder created.
|
||||||
|
Permission os.FileMode `mapstructure:"permission" json:"permission" yaml:"permission" toml:"permission"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NutsDBFolder) Validate() liberr.Error {
|
||||||
|
val := validator.New()
|
||||||
|
err := val.Struct(f)
|
||||||
|
|
||||||
|
if e, ok := err.(*validator.InvalidValidationError); ok {
|
||||||
|
return ErrorValidateConfig.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := ErrorValidateConfig.Error(nil)
|
||||||
|
|
||||||
|
for _, e := range err.(validator.ValidationErrors) {
|
||||||
|
//nolint goerr113
|
||||||
|
out.AddParent(fmt.Errorf("config field '%s' is not validated by constraint '%s'", e.Field(), e.ActualTag()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if out.HasParent() {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NutsDBFolder) getDirectory(base, dir string) (string, liberr.Error) {
|
||||||
|
if f.Permission == 0 {
|
||||||
|
f.Permission = 0770
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
abs string
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(dir) < 1 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(base) > 0 {
|
||||||
|
dir = filepath.Join(base, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
if abs, err = filepath.Abs(dir); err != nil {
|
||||||
|
return "", ErrorFolderCheck.ErrorParent(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(abs); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return "", ErrorFolderCheck.ErrorParent(err)
|
||||||
|
} else if err != nil {
|
||||||
|
if err = os.MkdirAll(abs, f.Permission); err != nil {
|
||||||
|
return "", ErrorFolderCreate.ErrorParent(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return abs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NutsDBFolder) GetDirectoryBase() (string, liberr.Error) {
|
||||||
|
return f.getDirectory("", f.Base)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NutsDBFolder) GetDirectoryData() (string, liberr.Error) {
|
||||||
|
if base, err := f.GetDirectoryBase(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs, err := f.getDirectory(base, f.Data); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs == "" {
|
||||||
|
return f.getDirectory(base, _DefaultFolderData)
|
||||||
|
} else {
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NutsDBFolder) GetDirectoryBackup() (string, liberr.Error) {
|
||||||
|
if base, err := f.GetDirectoryBase(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs, err := f.getDirectory(base, f.Backup); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs == "" {
|
||||||
|
return f.getDirectory(base, _DefaultFolderBackup)
|
||||||
|
} else {
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NutsDBFolder) GetDirectoryWal() (string, liberr.Error) {
|
||||||
|
if base, err := f.GetDirectoryBase(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs, err := f.getDirectory(base, f.WalDir); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs == "" {
|
||||||
|
return f.getDirectory(base, _DefaultFolderWal)
|
||||||
|
} else {
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NutsDBFolder) GetDirectoryHost() (string, liberr.Error) {
|
||||||
|
if base, err := f.GetDirectoryBase(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs, err := f.getDirectory(base, f.HostDir); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs == "" {
|
||||||
|
return f.getDirectory(base, _DefaultFolderHost)
|
||||||
|
} else {
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f NutsDBFolder) GetDirectoryTemp() (string, liberr.Error) {
|
||||||
|
if base, err := f.GetDirectoryBase(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs, err := f.getDirectory(base, f.Temp); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if fs == "" {
|
||||||
|
return f.getDirectory("", os.TempDir())
|
||||||
|
} else {
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
}
|
242
nutsdb/entryKv.go
Normal file
242
nutsdb/entryKv.go
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/fxamacker/cbor/v2"
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
liblog "github.com/nabbar/golib/logger"
|
||||||
|
"github.com/xujiajun/nutsdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_MinSkipCaller = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommandRequest struct {
|
||||||
|
Cmd CmdCode `mapstructure:"cmd" json:"cmd" yaml:"cmd" toml:"cmd" cbor:"cmd"`
|
||||||
|
Params []interface{} `mapstructure:"params" json:"params" yaml:"params" toml:"params" cbor:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandResponse struct {
|
||||||
|
Error error `mapstructure:"error" json:"error" yaml:"error" toml:"error" cbor:"error"`
|
||||||
|
Value []interface{} `mapstructure:"value" json:"value" yaml:"value" toml:"value" cbor:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommand() *CommandRequest {
|
||||||
|
return &CommandRequest{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandByDecode(p []byte) (*CommandRequest, liberr.Error) {
|
||||||
|
d := CommandRequest{}
|
||||||
|
|
||||||
|
if e := cbor.Unmarshal(p, &d); e != nil {
|
||||||
|
return nil, ErrorCommandUnmarshal.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &d, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommandByCaller(params ...interface{}) *CommandRequest {
|
||||||
|
pc := make([]uintptr, 10) // at least 1 entry needed
|
||||||
|
runtime.Callers(_MinSkipCaller, pc)
|
||||||
|
f := runtime.FuncForPC(pc[0])
|
||||||
|
|
||||||
|
d := &CommandRequest{}
|
||||||
|
d.Cmd = CmdCodeFromName(f.Name())
|
||||||
|
|
||||||
|
if len(params) > 0 {
|
||||||
|
d.Params = params
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandRequest) InitParams(num int) {
|
||||||
|
c.Params = make([]interface{}, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandRequest) InitParamsCounter(num int) int {
|
||||||
|
c.InitParams(num)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandRequest) SetParams(num int, val interface{}) {
|
||||||
|
if num < len(c.Params) {
|
||||||
|
c.Params[num] = val
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := c.Params
|
||||||
|
c.Params = make([]interface{}, len(c.Params)+1)
|
||||||
|
|
||||||
|
if len(tmp) > 0 {
|
||||||
|
for i := 0; i < len(tmp); i++ {
|
||||||
|
c.Params[i] = tmp[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Params[num] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandRequest) SetParamsInc(num int, val interface{}) int {
|
||||||
|
c.SetParams(num, val)
|
||||||
|
num++
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandRequest) EncodeRequest() ([]byte, liberr.Error) {
|
||||||
|
if p, e := cbor.Marshal(c); e != nil {
|
||||||
|
return nil, ErrorCommandMarshal.ErrorParent(e)
|
||||||
|
} else {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandRequest) DecodeResult(p []byte) (*CommandResponse, liberr.Error) {
|
||||||
|
res := CommandResponse{}
|
||||||
|
|
||||||
|
if e := cbor.Unmarshal(p, &res); e != nil {
|
||||||
|
return nil, ErrorCommandResultUnmarshal.ErrorParent(e)
|
||||||
|
} else {
|
||||||
|
return &res, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandRequest) RunLocal(tx *nutsdb.Tx) (*CommandResponse, liberr.Error) {
|
||||||
|
if tx == nil {
|
||||||
|
return nil, ErrorTransactionClosed.Error(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Cmd == CmdUnknown {
|
||||||
|
return nil, ErrorClientCommandInvalid.Error(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
valTx := reflect.ValueOf(tx)
|
||||||
|
mtName := c.Cmd.Name()
|
||||||
|
method := valTx.MethodByName(mtName)
|
||||||
|
nbPrm := method.Type().NumIn()
|
||||||
|
|
||||||
|
if len(c.Params) != nbPrm {
|
||||||
|
//nolint #goerr113
|
||||||
|
return nil, ErrorClientCommandParamsBadNumber.ErrorParent(fmt.Errorf("%s need %d parameters", c.Cmd.Name(), nbPrm))
|
||||||
|
}
|
||||||
|
|
||||||
|
params := make([]reflect.Value, nbPrm)
|
||||||
|
for i := 0; i < nbPrm; i++ {
|
||||||
|
v := reflect.ValueOf(c.Params[i])
|
||||||
|
liblog.DebugLevel.Logf("Param %d : type %s - Val %v", i, v.Type().Name(), v.Interface())
|
||||||
|
|
||||||
|
if v.Type().Kind() == method.Type().In(i).Kind() {
|
||||||
|
params[i] = v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.Type().ConvertibleTo(method.Type().In(i)) {
|
||||||
|
//nolint #goerr113
|
||||||
|
return nil, ErrorClientCommandParamsMismatching.ErrorParent(fmt.Errorf("cmd: %s", mtName), fmt.Errorf("param num: %d", i), fmt.Errorf("param type: %s, avaitting type: %s", v.Type().Kind(), method.Type().In(i).Kind()))
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint #exhaustive
|
||||||
|
switch method.Type().In(i).Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
params[i] = reflect.ValueOf(v.Bool())
|
||||||
|
case reflect.Int:
|
||||||
|
params[i] = reflect.ValueOf(int(v.Int()))
|
||||||
|
case reflect.Int8:
|
||||||
|
params[i] = reflect.ValueOf(int8(v.Int()))
|
||||||
|
case reflect.Int16:
|
||||||
|
params[i] = reflect.ValueOf(int8(v.Int()))
|
||||||
|
case reflect.Int32:
|
||||||
|
params[i] = reflect.ValueOf(int16(v.Int()))
|
||||||
|
case reflect.Int64:
|
||||||
|
params[i] = reflect.ValueOf(v.Int())
|
||||||
|
case reflect.Uintptr:
|
||||||
|
params[i] = reflect.ValueOf(v.UnsafeAddr())
|
||||||
|
case reflect.Uint:
|
||||||
|
params[i] = reflect.ValueOf(uint(v.Uint()))
|
||||||
|
case reflect.Uint8:
|
||||||
|
params[i] = reflect.ValueOf(uint8(v.Uint()))
|
||||||
|
case reflect.Uint16:
|
||||||
|
params[i] = reflect.ValueOf(uint16(v.Uint()))
|
||||||
|
case reflect.Uint32:
|
||||||
|
params[i] = reflect.ValueOf(uint32(v.Uint()))
|
||||||
|
case reflect.Uint64:
|
||||||
|
params[i] = reflect.ValueOf(v.Uint())
|
||||||
|
case reflect.Float32:
|
||||||
|
params[i] = reflect.ValueOf(float32(v.Float()))
|
||||||
|
case reflect.Float64:
|
||||||
|
params[i] = reflect.ValueOf(v.Float())
|
||||||
|
case reflect.Complex64:
|
||||||
|
params[i] = reflect.ValueOf(complex64(v.Complex()))
|
||||||
|
case reflect.Complex128:
|
||||||
|
params[i] = reflect.ValueOf(v.Complex())
|
||||||
|
case reflect.Interface:
|
||||||
|
params[i] = reflect.ValueOf(v.Interface())
|
||||||
|
case reflect.String:
|
||||||
|
params[i] = reflect.ValueOf(v.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
liblog.DebugLevel.Logf("Change Param %d : type %s to %v", i, v.Type().Name(), params[i].Type().Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := method.Call(params)
|
||||||
|
ret := CommandResponse{
|
||||||
|
Error: nil,
|
||||||
|
Value: make([]interface{}, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(resp); i++ {
|
||||||
|
v := resp[i].Interface()
|
||||||
|
if e, ok := v.(error); ok {
|
||||||
|
ret.Error = e
|
||||||
|
} else {
|
||||||
|
ret.Value = append(ret.Value, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.Error == nil && len(ret.Value) < 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CommandRequest) Run(tx *nutsdb.Tx) ([]byte, liberr.Error) {
|
||||||
|
if r, err := c.RunLocal(tx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if p, e := cbor.Marshal(r); e != nil {
|
||||||
|
return nil, ErrorCommandResultMarshal.ErrorParent(e)
|
||||||
|
} else {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
150
nutsdb/errors.go
Normal file
150
nutsdb/errors.go
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import liberr "github.com/nabbar/golib/errors"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorParamsEmpty liberr.CodeError = iota + liberr.MinPkgNutsDB
|
||||||
|
ErrorParamsMissing
|
||||||
|
ErrorParamsMismatching
|
||||||
|
ErrorValidateConfig
|
||||||
|
ErrorValidateNutsDB
|
||||||
|
ErrorClusterInit
|
||||||
|
ErrorFolderCheck
|
||||||
|
ErrorFolderCreate
|
||||||
|
ErrorFolderCopy
|
||||||
|
ErrorFolderDelete
|
||||||
|
ErrorFolderExtract
|
||||||
|
ErrorFolderArchive
|
||||||
|
ErrorFolderCompress
|
||||||
|
ErrorDatabaseClosed
|
||||||
|
ErrorDatabaseKeyInvalid
|
||||||
|
ErrorDatabaseBackup
|
||||||
|
ErrorDatabaseSnapshot
|
||||||
|
ErrorTransactionInit
|
||||||
|
ErrorTransactionClosed
|
||||||
|
ErrorTransactionCommit
|
||||||
|
ErrorTransactionPutKey
|
||||||
|
ErrorCommandInvalid
|
||||||
|
ErrorCommandUnmarshal
|
||||||
|
ErrorCommandMarshal
|
||||||
|
ErrorCommandResultUnmarshal
|
||||||
|
ErrorCommandResultMarshal
|
||||||
|
ErrorLogEntryAdd
|
||||||
|
ErrorClientCommandInvalid
|
||||||
|
ErrorClientCommandParamsBadNumber
|
||||||
|
ErrorClientCommandParamsMismatching
|
||||||
|
ErrorClientCommandCall
|
||||||
|
ErrorClientCommandCommit
|
||||||
|
ErrorClientCommandResponseInvalid
|
||||||
|
)
|
||||||
|
|
||||||
|
var isCodeError = false
|
||||||
|
|
||||||
|
func IsCodeError() bool {
|
||||||
|
return isCodeError
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
isCodeError = liberr.ExistInMapMessage(ErrorParamsEmpty)
|
||||||
|
liberr.RegisterIdFctMessage(ErrorParamsEmpty, getMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMessage(code liberr.CodeError) (message string) {
|
||||||
|
switch code {
|
||||||
|
case liberr.UNK_ERROR:
|
||||||
|
return ""
|
||||||
|
case ErrorParamsEmpty:
|
||||||
|
return "at least on given parameters is empty"
|
||||||
|
case ErrorParamsMissing:
|
||||||
|
return "at least on given parameters is missing"
|
||||||
|
case ErrorParamsMismatching:
|
||||||
|
return "at least on given parameters is mismatching awaiting type"
|
||||||
|
case ErrorValidateConfig:
|
||||||
|
return "config seems to be invalid"
|
||||||
|
case ErrorValidateNutsDB:
|
||||||
|
return "database config seems to be invalid"
|
||||||
|
case ErrorClusterInit:
|
||||||
|
return "cannot start or join cluster"
|
||||||
|
case ErrorFolderCheck:
|
||||||
|
return "error while trying to check or stat folder"
|
||||||
|
case ErrorFolderCreate:
|
||||||
|
return "error while trying to create folder"
|
||||||
|
case ErrorFolderCopy:
|
||||||
|
return "error while trying to copy folder"
|
||||||
|
case ErrorFolderArchive:
|
||||||
|
return "error while trying to archive folder"
|
||||||
|
case ErrorFolderCompress:
|
||||||
|
return "error while trying to compress folder"
|
||||||
|
case ErrorFolderExtract:
|
||||||
|
return "error while trying to extract snapshot archive"
|
||||||
|
case ErrorDatabaseClosed:
|
||||||
|
return "database is closed"
|
||||||
|
case ErrorDatabaseKeyInvalid:
|
||||||
|
return "database key seems to be invalid"
|
||||||
|
case ErrorDatabaseBackup:
|
||||||
|
return "error occurs while trying to backup database folder"
|
||||||
|
case ErrorDatabaseSnapshot:
|
||||||
|
return "error occurs while trying to backup database to cluster members"
|
||||||
|
case ErrorTransactionInit:
|
||||||
|
return "cannot initialize new transaction from database"
|
||||||
|
case ErrorTransactionClosed:
|
||||||
|
return "transaction is closed"
|
||||||
|
case ErrorTransactionCommit:
|
||||||
|
return "cannot commit transaction writable into database"
|
||||||
|
case ErrorTransactionPutKey:
|
||||||
|
return "cannot send Put command into database transaction"
|
||||||
|
case ErrorCommandInvalid:
|
||||||
|
return "given query is not a valid DB command"
|
||||||
|
case ErrorCommandUnmarshal:
|
||||||
|
return "cannot unmarshall DB command"
|
||||||
|
case ErrorCommandMarshal:
|
||||||
|
return "cannot marshall DB command"
|
||||||
|
case ErrorCommandResultUnmarshal:
|
||||||
|
return "cannot unmarshall DB command result"
|
||||||
|
case ErrorCommandResultMarshal:
|
||||||
|
return "cannot marshall DB command result"
|
||||||
|
case ErrorLogEntryAdd:
|
||||||
|
return "cannot add key/value to database"
|
||||||
|
case ErrorClientCommandInvalid:
|
||||||
|
return "invalid command"
|
||||||
|
case ErrorClientCommandParamsBadNumber:
|
||||||
|
return "invalid number of parameters for client command"
|
||||||
|
case ErrorClientCommandParamsMismatching:
|
||||||
|
return "invalid type of parameter for client command"
|
||||||
|
case ErrorClientCommandCall:
|
||||||
|
return "error occurs while running client command"
|
||||||
|
case ErrorClientCommandCommit:
|
||||||
|
return "error occurs while commit client command"
|
||||||
|
case ErrorClientCommandResponseInvalid:
|
||||||
|
return "response of requested client command seems to be invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
65
nutsdb/interface.go
Normal file
65
nutsdb/interface.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
libclu "github.com/nabbar/golib/cluster"
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NutsDB interface {
|
||||||
|
Listen() liberr.Error
|
||||||
|
Restart() liberr.Error
|
||||||
|
Shutdown() liberr.Error
|
||||||
|
|
||||||
|
ForceRestart()
|
||||||
|
ForceShutdown()
|
||||||
|
|
||||||
|
IsRunning() bool
|
||||||
|
IsReady(ctx context.Context) bool
|
||||||
|
WaitReady(ctx context.Context, tick time.Duration)
|
||||||
|
|
||||||
|
//StatusInfo() (name string, release string, hash string)
|
||||||
|
//StatusHealth() error
|
||||||
|
//StatusRoute(prefix string, fctMessage status.FctMessage, sts status.RouteStatus)
|
||||||
|
|
||||||
|
Cluster() libclu.Cluster
|
||||||
|
Client(ctx context.Context, tickSync time.Duration) Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(c Config) NutsDB {
|
||||||
|
return &ndb{
|
||||||
|
c: c,
|
||||||
|
t: new(atomic.Value),
|
||||||
|
r: new(atomic.Value),
|
||||||
|
}
|
||||||
|
}
|
203
nutsdb/model.go
Normal file
203
nutsdb/model.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dgbstm "github.com/lni/dragonboat/v3/statemachine"
|
||||||
|
libclu "github.com/nabbar/golib/cluster"
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ndb struct {
|
||||||
|
c Config
|
||||||
|
t *atomic.Value
|
||||||
|
r *atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) createNodeMachine(node uint64, cluster uint64) dgbstm.IOnDiskStateMachine {
|
||||||
|
var (
|
||||||
|
err liberr.Error
|
||||||
|
opt Options
|
||||||
|
)
|
||||||
|
|
||||||
|
if opt, err = n.c.GetOptions(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNode(node, cluster, opt, n.setRunning)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) newCluster() liberr.Error {
|
||||||
|
var (
|
||||||
|
clu libclu.Cluster
|
||||||
|
err liberr.Error
|
||||||
|
cfg libclu.Config
|
||||||
|
)
|
||||||
|
|
||||||
|
if i := n.t.Load(); i != nil {
|
||||||
|
if err = n.Shutdown(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg, err = n.c.GetConfigCluster(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clu, err = libclu.NewCluster(cfg, nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
clu.SetFctCreateSTMOnDisk(n.createNodeMachine)
|
||||||
|
n.t.Store(clu)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) IsRunning() bool {
|
||||||
|
if i := n.r.Load(); i == nil {
|
||||||
|
return false
|
||||||
|
} else if b, ok := i.(bool); !ok {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) setRunning(state bool) {
|
||||||
|
if n == nil || n.r == nil {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
n.r.Store(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) IsReady(ctx context.Context) bool {
|
||||||
|
if m, e := n.Cluster().SyncGetClusterMembership(ctx); e != nil || m == nil || len(m.Nodes) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok, e := n.Cluster().GetLeaderID(); e != nil || !ok {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) WaitReady(ctx context.Context, tick time.Duration) {
|
||||||
|
for {
|
||||||
|
if n.IsRunning() && n.IsReady(ctx) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(tick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) Listen() liberr.Error {
|
||||||
|
var (
|
||||||
|
c libclu.Cluster
|
||||||
|
e liberr.Error
|
||||||
|
)
|
||||||
|
|
||||||
|
if c = n.Cluster(); c == nil {
|
||||||
|
if e = n.newCluster(); e != nil {
|
||||||
|
return e
|
||||||
|
} else if c = n.Cluster(); c == nil {
|
||||||
|
return ErrorClusterInit.Error(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e = c.ClusterStart(len(n.c.Cluster.InitMember) < 1); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
n.t.Store(c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) Restart() liberr.Error {
|
||||||
|
return n.Listen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) ForceRestart() {
|
||||||
|
n.ForceShutdown()
|
||||||
|
_ = n.Listen()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) Shutdown() liberr.Error {
|
||||||
|
if i := n.t.Load(); i == nil {
|
||||||
|
return nil
|
||||||
|
} else if c, ok := i.(libclu.Cluster); !ok {
|
||||||
|
return nil
|
||||||
|
} else if !c.HasNodeInfo(0) {
|
||||||
|
return nil
|
||||||
|
} else if err := c.NodeStop(0); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) ForceShutdown() {
|
||||||
|
if err := n.Shutdown(); err == nil {
|
||||||
|
return
|
||||||
|
} else if i := n.t.Load(); i == nil {
|
||||||
|
return
|
||||||
|
} else if c, ok := i.(libclu.Cluster); !ok {
|
||||||
|
return
|
||||||
|
} else if !c.HasNodeInfo(0) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
_ = c.ClusterStop(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) Cluster() libclu.Cluster {
|
||||||
|
if i := n.t.Load(); i == nil {
|
||||||
|
return nil
|
||||||
|
} else if c, ok := i.(libclu.Cluster); !ok {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ndb) Client(ctx context.Context, tickSync time.Duration) Client {
|
||||||
|
return &clientNutDB{
|
||||||
|
x: ctx,
|
||||||
|
t: tickSync,
|
||||||
|
c: n.Cluster,
|
||||||
|
w: n.WaitReady,
|
||||||
|
}
|
||||||
|
}
|
354
nutsdb/node.go
Normal file
354
nutsdb/node.go
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
dgbstm "github.com/lni/dragonboat/v3/statemachine"
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
"github.com/xujiajun/nutsdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
_RaftBucket = "_raft"
|
||||||
|
_RaftKeyAppliedIndex = "_raft_applied_index"
|
||||||
|
_WordByteTrue = 0xff
|
||||||
|
)
|
||||||
|
|
||||||
|
func newNode(node uint64, cluster uint64, opt Options, fct func(state bool)) dgbstm.IOnDiskStateMachine {
|
||||||
|
if fct == nil {
|
||||||
|
fct = func(state bool) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &nutsNode{
|
||||||
|
n: node,
|
||||||
|
c: cluster,
|
||||||
|
o: opt,
|
||||||
|
r: fct,
|
||||||
|
d: new(atomic.Value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nutsNode struct {
|
||||||
|
n uint64 // nodeId
|
||||||
|
c uint64 // clusterId
|
||||||
|
o Options // options nutsDB
|
||||||
|
r func(state bool) // is running
|
||||||
|
d *atomic.Value // nutsDB database pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) setRunning(state bool) {
|
||||||
|
if n != nil && n.r != nil {
|
||||||
|
n.r(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) newTx(writable bool) (*nutsdb.Tx, liberr.Error) {
|
||||||
|
if n == nil || n.d == nil {
|
||||||
|
return nil, ErrorDatabaseClosed.Error(nil)
|
||||||
|
}
|
||||||
|
if i := n.d.Load(); i == nil {
|
||||||
|
return nil, ErrorDatabaseClosed.Error(nil)
|
||||||
|
} else if db, ok := i.(*nutsdb.DB); !ok {
|
||||||
|
return nil, ErrorDatabaseClosed.Error(nil)
|
||||||
|
} else if tx, e := db.Begin(writable); e != nil {
|
||||||
|
return nil, ErrorTransactionInit.ErrorParent(e)
|
||||||
|
} else {
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) getRaftLogIndexLastApplied() (idxRaftlog uint64, err error) {
|
||||||
|
var (
|
||||||
|
tx *nutsdb.Tx
|
||||||
|
en *nutsdb.Entry
|
||||||
|
)
|
||||||
|
|
||||||
|
if tx, err = n.newTx(false); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if en, err = tx.Get(_RaftBucket, []byte(_RaftKeyAppliedIndex)); err != nil && errors.Is(err, nutsdb.ErrBucketNotFound) {
|
||||||
|
return 0, nil
|
||||||
|
} else if err != nil && errors.Is(err, nutsdb.ErrBucketEmpty) {
|
||||||
|
return 0, nil
|
||||||
|
} else if err != nil && errors.Is(err, nutsdb.ErrNotFoundKey) {
|
||||||
|
return 0, nil
|
||||||
|
} else if err != nil && errors.Is(err, nutsdb.ErrKeyNotFound) {
|
||||||
|
return 0, nil
|
||||||
|
} else if err != nil && errors.Is(err, nutsdb.ErrKeyEmpty) {
|
||||||
|
return 0, nil
|
||||||
|
} else if err != nil && strings.HasPrefix(err.Error(), "not found bucket") {
|
||||||
|
return 0, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if en.IsZero() {
|
||||||
|
return 0, nil
|
||||||
|
} else {
|
||||||
|
return n.btoi64(en.Value), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) i64tob(val uint64) []byte {
|
||||||
|
r := make([]byte, 8)
|
||||||
|
for i := uint64(0); i < 8; i++ {
|
||||||
|
r[i] = byte((val >> (i * 8)) & _WordByteTrue)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) btoi64(val []byte) uint64 {
|
||||||
|
r := uint64(0)
|
||||||
|
for i := uint64(0); i < 8; i++ {
|
||||||
|
r |= uint64(val[i]) << (8 * i)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) applyRaftLogIndexLastApplied(idx uint64) error {
|
||||||
|
var (
|
||||||
|
e error
|
||||||
|
tx *nutsdb.Tx
|
||||||
|
err liberr.Error
|
||||||
|
)
|
||||||
|
|
||||||
|
if tx, err = n.newTx(true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if e = tx.Put(_RaftBucket, []byte(_RaftKeyAppliedIndex), n.i64tob(idx), 0); e != nil {
|
||||||
|
return ErrorTransactionPutKey.ErrorParent(e)
|
||||||
|
} else if e = tx.Commit(); e != nil {
|
||||||
|
return ErrorTransactionCommit.ErrorParent(e)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open @TODO : analyze channel role !!
|
||||||
|
func (n *nutsNode) Open(stopc <-chan struct{}) (idxRaftlog uint64, err error) {
|
||||||
|
var db *nutsdb.DB
|
||||||
|
|
||||||
|
if db, err = nutsdb.Open(n.o.NutsDBOptions()); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
n.d.Store(db)
|
||||||
|
n.setRunning(true)
|
||||||
|
|
||||||
|
if idxRaftlog, err = n.getRaftLogIndexLastApplied(); err != nil {
|
||||||
|
_ = n.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) Close() error {
|
||||||
|
defer n.setRunning(false)
|
||||||
|
|
||||||
|
if n == nil || n.d == nil {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else if i := n.d.Load(); i == nil {
|
||||||
|
return nil
|
||||||
|
} else if db, ok := i.(*nutsdb.DB); !ok {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return db.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) Update(logEntry []dgbstm.Entry) ([]dgbstm.Entry, error) {
|
||||||
|
var (
|
||||||
|
e error
|
||||||
|
|
||||||
|
tx *nutsdb.Tx
|
||||||
|
kv *CommandRequest
|
||||||
|
|
||||||
|
err liberr.Error
|
||||||
|
res []byte
|
||||||
|
idx int
|
||||||
|
ent dgbstm.Entry
|
||||||
|
)
|
||||||
|
|
||||||
|
if tx, err = n.newTx(true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, ent = range logEntry {
|
||||||
|
if kv, err = NewCommandByDecode(ent.Cmd); err != nil {
|
||||||
|
logEntry[idx].Result = dgbstm.Result{
|
||||||
|
Value: 0,
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return logEntry, err
|
||||||
|
} else if res, err = kv.Run(tx); err != nil {
|
||||||
|
logEntry[idx].Result = dgbstm.Result{
|
||||||
|
Value: 0,
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return logEntry, err
|
||||||
|
} else {
|
||||||
|
logEntry[idx].Result = dgbstm.Result{
|
||||||
|
Value: uint64(idx),
|
||||||
|
Data: res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if e = tx.Commit(); e != nil {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
return logEntry, ErrorTransactionCommit.ErrorParent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return logEntry, n.applyRaftLogIndexLastApplied(logEntry[len(logEntry)-1].Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) Lookup(query interface{}) (value interface{}, err error) {
|
||||||
|
var (
|
||||||
|
t *nutsdb.Tx
|
||||||
|
r *CommandResponse
|
||||||
|
c *CommandRequest
|
||||||
|
e liberr.Error
|
||||||
|
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if t, e = n.newTx(true); e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if t != nil {
|
||||||
|
_ = t.Rollback()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if c, ok = query.(*CommandRequest); !ok {
|
||||||
|
return nil, ErrorCommandInvalid.Error(nil)
|
||||||
|
} else if r, e = c.RunLocal(t); e != nil {
|
||||||
|
return nil, e
|
||||||
|
} else {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) Sync() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) PrepareSnapshot() (interface{}, error) {
|
||||||
|
if n == nil || n.d == nil {
|
||||||
|
return nil, ErrorDatabaseClosed.Error(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sh = newSnap()
|
||||||
|
|
||||||
|
if i := n.d.Load(); i == nil {
|
||||||
|
sh.Finish()
|
||||||
|
return nil, ErrorDatabaseClosed.Error(nil)
|
||||||
|
} else if db, ok := i.(*nutsdb.DB); !ok {
|
||||||
|
sh.Finish()
|
||||||
|
return nil, ErrorDatabaseClosed.Error(nil)
|
||||||
|
} else if err := sh.Prepare(n.o, db); err != nil {
|
||||||
|
sh.Finish()
|
||||||
|
return nil, ErrorDatabaseBackup.ErrorParent(err)
|
||||||
|
} else {
|
||||||
|
return sh, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) SaveSnapshot(i interface{}, writer io.Writer, c <-chan struct{}) error {
|
||||||
|
if n == nil || n.d == nil {
|
||||||
|
return ErrorDatabaseClosed.Error(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == nil {
|
||||||
|
return ErrorParamsEmpty.Error(nil)
|
||||||
|
} else if sh, ok := snapCast(i); !ok {
|
||||||
|
return ErrorParamsMismatching.Error(nil)
|
||||||
|
} else if err := sh.Save(n.o, writer); err != nil {
|
||||||
|
sh.Finish()
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
sh.Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *nutsNode) RecoverFromSnapshot(reader io.Reader, c <-chan struct{}) error {
|
||||||
|
if n == nil || n.d == nil {
|
||||||
|
return ErrorDatabaseClosed.Error(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := newSnap()
|
||||||
|
defer s.Finish()
|
||||||
|
|
||||||
|
if err := s.Load(n.o, reader); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i := n.d.Load(); i == nil {
|
||||||
|
if err := s.Apply(n.o); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if db, ok := i.(*nutsdb.DB); !ok {
|
||||||
|
if err := s.Apply(n.o); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = db.Close()
|
||||||
|
if err := s.Apply(n.o); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//@TODO : check channel is ok....
|
||||||
|
if _, err := n.Open(nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
210
nutsdb/options.go
Normal file
210
nutsdb/options.go
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
"github.com/xujiajun/nutsdb"
|
||||||
|
"github.com/xujiajun/utils/filesystem"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options interface {
|
||||||
|
NutsDBOptions() nutsdb.Options
|
||||||
|
|
||||||
|
NewBackup(db *nutsdb.DB) (string, liberr.Error)
|
||||||
|
NewBackupTemp(db *nutsdb.DB) (string, liberr.Error)
|
||||||
|
|
||||||
|
NewTempFolder() (string, liberr.Error)
|
||||||
|
NewTempFile(extension string) (string, liberr.Error)
|
||||||
|
|
||||||
|
CleanBackup() liberr.Error
|
||||||
|
Permission() os.FileMode
|
||||||
|
|
||||||
|
RestoreBackup(dir string) liberr.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOptions(cfgNuts NutsDBOptions, cfgFs NutsDBFolder) (Options, liberr.Error) {
|
||||||
|
if _, err := cfgFs.GetDirectoryBase(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o := &options{
|
||||||
|
limit: cfgFs.LimitNumberBackup,
|
||||||
|
perm: cfgFs.Permission,
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs, err := cfgFs.GetDirectoryData(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
o.dirs.data = fs
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs, err := cfgFs.GetDirectoryBackup(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
o.dirs.backup = fs
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs, err := cfgFs.GetDirectoryTemp(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
o.dirs.temp = fs
|
||||||
|
}
|
||||||
|
|
||||||
|
o.nuts = cfgNuts.GetNutsDBOptions(o.dirs.data)
|
||||||
|
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
nuts nutsdb.Options
|
||||||
|
dirs struct {
|
||||||
|
data string
|
||||||
|
backup string
|
||||||
|
temp string
|
||||||
|
}
|
||||||
|
limit uint8
|
||||||
|
perm os.FileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) NutsDBOptions() nutsdb.Options {
|
||||||
|
return o.nuts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) NewBackup(db *nutsdb.DB) (string, liberr.Error) {
|
||||||
|
fld := o.getBackupDirName()
|
||||||
|
|
||||||
|
if e := os.MkdirAll(filepath.Join(o.dirs.backup, fld), o.perm); e != nil {
|
||||||
|
return "", ErrorFolderCreate.ErrorParent(e)
|
||||||
|
} else if err := o.newBackupDir(fld, db); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return fld, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) NewBackupTemp(db *nutsdb.DB) (string, liberr.Error) {
|
||||||
|
if fld, err := o.NewTempFolder(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if err = o.newBackupDir(fld, db); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return fld, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) NewTempFolder() (string, liberr.Error) {
|
||||||
|
if p, e := ioutil.TempDir(o.dirs.temp, o.getTempPrefix()); e != nil {
|
||||||
|
return "", ErrorFolderCreate.ErrorParent(e)
|
||||||
|
} else {
|
||||||
|
_ = os.Chmod(p, o.perm)
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) NewTempFile(extension string) (string, liberr.Error) {
|
||||||
|
pattern := o.getTempPrefix() + "-*"
|
||||||
|
|
||||||
|
if extension != "" {
|
||||||
|
pattern = pattern + "." + extension
|
||||||
|
}
|
||||||
|
|
||||||
|
if file, e := ioutil.TempFile(o.dirs.temp, pattern); e != nil {
|
||||||
|
return "", ErrorFolderCreate.ErrorParent(e)
|
||||||
|
} else {
|
||||||
|
p := file.Name()
|
||||||
|
_ = file.Close()
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) CleanBackup() liberr.Error {
|
||||||
|
panic("implement me")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) Permission() os.FileMode {
|
||||||
|
return o.perm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) RestoreBackup(dir string) liberr.Error {
|
||||||
|
if err := os.RemoveAll(o.dirs.data); err != nil {
|
||||||
|
return ErrorFolderDelete.ErrorParent(err)
|
||||||
|
} else if err = filesystem.CopyDir(dir, o.dirs.data); err != nil {
|
||||||
|
return ErrorFolderCopy.ErrorParent(err)
|
||||||
|
} else {
|
||||||
|
_ = os.Chmod(o.dirs.data, o.perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/// private
|
||||||
|
|
||||||
|
func (o options) newBackupDir(dir string, db *nutsdb.DB) liberr.Error {
|
||||||
|
if err := db.Backup(dir); err != nil {
|
||||||
|
return ErrorDatabaseBackup.ErrorParent(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) getTempPrefix() string {
|
||||||
|
b := make([]byte, 64)
|
||||||
|
|
||||||
|
b = b[:runtime.Stack(b, false)]
|
||||||
|
b = bytes.TrimPrefix(b, []byte("goroutine "))
|
||||||
|
b = b[:bytes.IndexByte(b, ' ')]
|
||||||
|
|
||||||
|
//nolint #nosec
|
||||||
|
/* #nosec */
|
||||||
|
n, _ := strconv.ParseUint(string(b), 10, 64)
|
||||||
|
|
||||||
|
return fmt.Sprintf("%d", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o options) getBackupDirName() string {
|
||||||
|
part := strings.Split(time.Now().Format(time.RFC3339), "T")
|
||||||
|
dt := part[0]
|
||||||
|
|
||||||
|
part = strings.Split(part[1], "Z")
|
||||||
|
tm := strings.Replace(part[0], ":", "-", -1)
|
||||||
|
tz := strings.Replace(part[1], ":", "", -1)
|
||||||
|
|
||||||
|
return dt + "T" + tm + "Z" + tz
|
||||||
|
}
|
175
nutsdb/snap.go
Normal file
175
nutsdb/snap.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/***********************************************************************************************************************
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021 Nicolas JUHEL
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
**********************************************************************************************************************/
|
||||||
|
|
||||||
|
package nutsdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/nabbar/golib/archive"
|
||||||
|
liberr "github.com/nabbar/golib/errors"
|
||||||
|
"github.com/nabbar/golib/ioutils"
|
||||||
|
"github.com/xujiajun/nutsdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Snapshot interface {
|
||||||
|
Prepare(opt Options, db *nutsdb.DB) liberr.Error
|
||||||
|
|
||||||
|
Save(opt Options, writer io.Writer) liberr.Error
|
||||||
|
Load(opt Options, reader io.Reader) liberr.Error
|
||||||
|
|
||||||
|
Apply(opt Options) liberr.Error
|
||||||
|
|
||||||
|
Finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSnap() Snapshot {
|
||||||
|
return &snap{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func snapCast(i interface{}) (Snapshot, bool) {
|
||||||
|
if sp, ok := i.(*snap); ok {
|
||||||
|
return sp, true
|
||||||
|
} else if ss, ok := i.(snap); ok {
|
||||||
|
return &ss, true
|
||||||
|
} else if si, ok := i.(Snapshot); ok {
|
||||||
|
return si, true
|
||||||
|
} else {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type snap struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snap) Prepare(opt Options, db *nutsdb.DB) liberr.Error {
|
||||||
|
if dir, err := opt.NewBackupTemp(db); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
s.path = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snap) Save(opt Options, writer io.Writer) liberr.Error {
|
||||||
|
var (
|
||||||
|
tar string
|
||||||
|
err error
|
||||||
|
|
||||||
|
t ioutils.FileProgress
|
||||||
|
e liberr.Error
|
||||||
|
)
|
||||||
|
|
||||||
|
if tar, e = opt.NewTempFile("tar"); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = os.Remove(tar)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if t, e = ioutils.NewFileProgressPathWrite(tar, false, true, 0664); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = t.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, e = archive.CreateArchive(archive.ArchiveTypeTarGzip, t, s.path); e != nil {
|
||||||
|
return ErrorFolderArchive.Error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = t.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return ErrorDatabaseSnapshot.ErrorParent(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = t.WriteTo(writer); err != nil {
|
||||||
|
return ErrorDatabaseSnapshot.ErrorParent(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snap) Load(opt Options, reader io.Reader) liberr.Error {
|
||||||
|
var (
|
||||||
|
arc string
|
||||||
|
out string
|
||||||
|
err error
|
||||||
|
|
||||||
|
a ioutils.FileProgress
|
||||||
|
e liberr.Error
|
||||||
|
)
|
||||||
|
|
||||||
|
if arc, e = opt.NewTempFile("tar.gz"); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = os.Remove(arc)
|
||||||
|
}()
|
||||||
|
|
||||||
|
if a, e = ioutils.NewFileProgressPathWrite(arc, false, true, 0664); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = a.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err = a.ReadFrom(reader); err != nil {
|
||||||
|
return ErrorDatabaseSnapshot.ErrorParent(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out, e = opt.NewTempFolder(); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
if e = archive.ExtractAll(a, path.Base(arc), out, opt.Permission()); e != nil {
|
||||||
|
return ErrorFolderExtract.Error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.path = out
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snap) Apply(opt Options) liberr.Error {
|
||||||
|
if e := opt.RestoreBackup(s.path); e != nil {
|
||||||
|
return ErrorDatabaseSnapshot.Error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *snap) Finish() {
|
||||||
|
_ = os.RemoveAll(s.path)
|
||||||
|
}
|
Reference in New Issue
Block a user