commit e9bcb96f3b5cadbbabc82f574bd28d6f5d006270 Author: tursom Date: Tue Apr 18 14:24:20 2023 +0800 create project diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2d2e8ac --- /dev/null +++ b/go.mod @@ -0,0 +1,15 @@ +module kvs + +go 1.20 + +require ( + github.com/mattn/go-sqlite3 v1.14.16 + github.com/syndtr/goleveldb v1.0.0 + github.com/tursom/GoCollections v0.2.7 + google.golang.org/protobuf v1.30.0 +) + +require ( + github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/timandy/routine v1.1.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..14d3d09 --- /dev/null +++ b/go.sum @@ -0,0 +1,45 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/timandy/routine v1.1.1 h1:6/Z7qLFZj3GrzuRksBFzIG8YGUh8CLhjnnMePBQTrEI= +github.com/timandy/routine v1.1.1/go.mod h1:OZHPOKSvqL/ZvqXFkNZyit0xIVelERptYXdAHH00adQ= +github.com/tursom/GoCollections v0.2.7 h1:WKbtQt1wuBoYhTXLCahr+BiFPJI8pUEQtsFuKk+V2Cc= +github.com/tursom/GoCollections v0.2.7/go.mod h1:uGlIIUhnuK01+AlzYtS/P+ZfqA2Q6MEulrPChiwf3KI= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/kv/ArrayCodec.go b/kv/ArrayCodec.go new file mode 100644 index 0000000..3516dda --- /dev/null +++ b/kv/ArrayCodec.go @@ -0,0 +1,59 @@ +package kv + +import ( + "bytes" + "io" + + "github.com/tursom/GoCollections/exceptions" + "github.com/tursom/GoCollections/lang" +) + +// 不推荐使用 + +type ( + arrayCodec[V any] struct { + lang.BaseObject + codec Codec[io.Reader, V] + } +) + +func ArrayCodec[V any](codec Codec[io.Reader, V]) Codec[[]byte, []V] { + return &arrayCodec[V]{codec: codec} +} + +func (a *arrayCodec[V]) encode(v2 []V) []byte { + var bs []byte + for _, v := range v2 { + encode := a.codec.encode(v) + all, err := io.ReadAll(encode) + if err != nil { + panic(exceptions.Package(err)) + } + + bs = append(bs, all...) + } + + return bs +} + +func (a *arrayCodec[V]) decode(v1 []byte) []V { + if len(v1) == 0 { + return []V{} + } + + reader := bytes.NewReader(v1) + + var values []V + + for func() bool { + defer recover() + + v := a.codec.decode(reader) + values = append(values, v) + + return reader.Len() > 0 + }() { + } + + return values +} diff --git a/kv/BoolCodec.go b/kv/BoolCodec.go new file mode 100644 index 0000000..5a493ba --- /dev/null +++ b/kv/BoolCodec.go @@ -0,0 +1,32 @@ +package kv + +import "github.com/tursom/GoCollections/lang" + +type ( + boolCodec struct { + lang.BaseObject + } +) + +var ( + TrueBytes = []byte{1} + FalseBytes = []byte{0} + + BoolToByteCodec Codec[[]byte, bool] = &boolCodec{} +) + +func (b *boolCodec) encode(v2 bool) []byte { + if v2 { + return TrueBytes + } else { + return FalseBytes + } +} + +func (b *boolCodec) decode(v1 []byte) bool { + if len(v1) == 0 { + return false + } + + return v1[0] != 0 +} diff --git a/kv/ChainCodec.go b/kv/ChainCodec.go new file mode 100644 index 0000000..d83e6c8 --- /dev/null +++ b/kv/ChainCodec.go @@ -0,0 +1,26 @@ +package kv + +import "github.com/tursom/GoCollections/lang" + +type ( + chainCodec[V1, V2, V3 any] struct { + lang.BaseObject + codec1 Codec[V2, V3] + codec2 Codec[V1, V2] + } +) + +func ChainCodec[V1, V2, V3 any](codec1 Codec[V2, V3], codec2 Codec[V1, V2]) Codec[V1, V3] { + return &chainCodec[V1, V2, V3]{ + codec1: codec1, + codec2: codec2, + } +} + +func (c *chainCodec[V1, V2, V3]) encode(v2 V3) V1 { + return c.codec2.encode(c.codec1.encode(v2)) +} + +func (c *chainCodec[V1, V2, V3]) decode(v1 V1) V3 { + return c.codec1.decode(c.codec2.decode(v1)) +} diff --git a/kv/MapKvs.go b/kv/MapKvs.go new file mode 100644 index 0000000..31a089a --- /dev/null +++ b/kv/MapKvs.go @@ -0,0 +1,28 @@ +package kv + +import ( + "github.com/tursom/GoCollections/exceptions" + "github.com/tursom/GoCollections/lang" +) + +type ( + mapKvs struct { + lang.BaseObject + m map[string][]byte + } +) + +func MapKvs() Store[string, []byte] { + return &mapKvs{ + m: make(map[string][]byte), + } +} + +func (m *mapKvs) Put(key string, value []byte) exceptions.Exception { + m.m[key] = value + return nil +} + +func (m *mapKvs) Get(key string) ([]byte, exceptions.Exception) { + return m.m[key], nil +} diff --git a/kv/MapKvs_test.go b/kv/MapKvs_test.go new file mode 100644 index 0000000..c1383e2 --- /dev/null +++ b/kv/MapKvs_test.go @@ -0,0 +1,16 @@ +package kv + +import ( + "fmt" + "testing" +) + +func Test_mapKvs(t *testing.T) { + kvs := CodecStore( + MapKvs(), + ByteToStringCodec, + ArrayCodec(ChainCodec(StringToByteCodec, LengthFieldCodec)), + ) + _ = kvs.Put([]byte{1}, []string{"hello", "world!"}) + fmt.Println(kvs.Get([]byte{1})) +} diff --git a/kv/NumberCodec.go b/kv/NumberCodec.go new file mode 100644 index 0000000..e61cefc --- /dev/null +++ b/kv/NumberCodec.go @@ -0,0 +1,235 @@ +package kv + +import ( + "encoding/binary" + "unsafe" + + "github.com/tursom/GoCollections/lang" +) + +type ( + int8ToByteCodec struct { + lang.BaseObject + } + int16ToByteCodec struct { + lang.BaseObject + } + int32ToByteCodec struct { + lang.BaseObject + } + int64ToByteCodec struct { + lang.BaseObject + } + uint8ToByteCodec struct { + lang.BaseObject + } + uint16ToByteCodec struct { + lang.BaseObject + } + uint32ToByteCodec struct { + lang.BaseObject + } + uint64ToByteCodec struct { + lang.BaseObject + } + float32ToByteCodec struct { + lang.BaseObject + } + float64ToByteCodec struct { + lang.BaseObject + } + complex64ToByteCodec struct { + lang.BaseObject + } + complex128ToByteCodec struct { + lang.BaseObject + } +) + +var ( + Int8ToByteCodec Codec[[]byte, int8] = &int8ToByteCodec{} + Int16ToByteCodec Codec[[]byte, int16] = &int16ToByteCodec{} + Int32ToByteCodec Codec[[]byte, int32] = &int32ToByteCodec{} + Int64ToByteCodec Codec[[]byte, int64] = &int64ToByteCodec{} + Uint8ToByteCodec Codec[[]byte, uint8] = &uint8ToByteCodec{} + Uint16ToByteCodec Codec[[]byte, uint16] = &uint16ToByteCodec{} + Uint32ToByteCodec Codec[[]byte, uint32] = &uint32ToByteCodec{} + Uint64ToByteCodec Codec[[]byte, uint64] = &uint64ToByteCodec{} + Float32ToByteCodec Codec[[]byte, float32] = &float32ToByteCodec{} + Float64ToByteCodec Codec[[]byte, float64] = &float64ToByteCodec{} + Complex64ToByteCodec Codec[[]byte, complex64] = &complex64ToByteCodec{} + Complex128ToByteCodec Codec[[]byte, complex128] = &complex128ToByteCodec{} + ByteToInt8Codec = InvertCodec[int8, []byte](&int8ToByteCodec{}) + ByteToInt16Codec = InvertCodec[int16, []byte](&int16ToByteCodec{}) + ByteToInt32Codec = InvertCodec[int32, []byte](&int32ToByteCodec{}) + ByteToInt64Codec = InvertCodec[int64, []byte](&int64ToByteCodec{}) + ByteToUint8Codec = InvertCodec[uint8, []byte](&uint8ToByteCodec{}) + ByteToUint16Codec = InvertCodec[uint16, []byte](&uint16ToByteCodec{}) + ByteToUint32Codec = InvertCodec[uint32, []byte](&uint32ToByteCodec{}) + ByteToUint64Codec = InvertCodec[uint64, []byte](&uint64ToByteCodec{}) + ByteToFloat32Codec = InvertCodec[float32, []byte](&float32ToByteCodec{}) + ByteToFloat64Codec = InvertCodec[float64, []byte](&float64ToByteCodec{}) + ByteToComplex64Codec = InvertCodec[complex64, []byte](&complex64ToByteCodec{}) + ByteToComplex128Codec = InvertCodec[complex128, []byte](&complex128ToByteCodec{}) +) + +func (u *int8ToByteCodec) encode(v2 int8) []byte { + return []byte{byte(v2)} +} + +func (u *int8ToByteCodec) decode(v1 []byte) int8 { + if len(v1) == 0 { + return 0 + } + + return int8(v1[0]) +} + +func (u *int16ToByteCodec) encode(v2 int16) []byte { + return binary.BigEndian.AppendUint16(nil, uint16(v2)) +} + +func (u *int16ToByteCodec) decode(v1 []byte) int16 { + if len(v1) == 0 { + return 0 + } + + return int16(binary.BigEndian.Uint16(v1)) +} + +func (u *int32ToByteCodec) encode(v2 int32) []byte { + return binary.BigEndian.AppendUint32(nil, uint32(v2)) +} + +func (u *int32ToByteCodec) decode(v1 []byte) int32 { + if len(v1) == 0 { + return 0 + } + + return int32(binary.BigEndian.Uint32(v1)) +} + +func (u *int64ToByteCodec) encode(v2 int64) []byte { + return binary.BigEndian.AppendUint64(nil, uint64(v2)) +} + +func (u *int64ToByteCodec) decode(v1 []byte) int64 { + if len(v1) == 0 { + return 0 + } + + return int64(binary.BigEndian.Uint64(v1)) +} + +func (u *uint8ToByteCodec) encode(v2 uint8) []byte { + return []byte{v2} +} + +func (u *uint8ToByteCodec) decode(v1 []byte) uint8 { + if len(v1) == 0 { + return 0 + } + + return v1[0] +} + +func (u *uint16ToByteCodec) encode(v2 uint16) []byte { + return binary.BigEndian.AppendUint16(nil, v2) +} + +func (u *uint16ToByteCodec) decode(v1 []byte) uint16 { + if len(v1) == 0 { + return 0 + } + + return binary.BigEndian.Uint16(v1) +} + +func (u *uint32ToByteCodec) encode(v2 uint32) []byte { + return binary.BigEndian.AppendUint32(nil, v2) +} + +func (u *uint32ToByteCodec) decode(v1 []byte) uint32 { + if len(v1) == 0 { + return 0 + } + + return binary.BigEndian.Uint32(v1) +} + +func (u *uint64ToByteCodec) encode(v2 uint64) []byte { + return binary.BigEndian.AppendUint64(nil, v2) +} + +func (u *uint64ToByteCodec) decode(v1 []byte) uint64 { + if len(v1) == 0 { + return 0 + } + + return binary.BigEndian.Uint64(v1) +} + +func (u *float32ToByteCodec) encode(v2 float32) []byte { + return binary.BigEndian.AppendUint32(nil, *(*uint32)(unsafe.Pointer(&v2))) +} + +func (u *float32ToByteCodec) decode(v1 []byte) float32 { + if len(v1) == 0 { + return 0 + } + + u2 := binary.BigEndian.Uint32(v1) + return *(*float32)(unsafe.Pointer(&u2)) +} + +func (u *float64ToByteCodec) encode(v2 float64) []byte { + return binary.BigEndian.AppendUint64(nil, *(*uint64)(unsafe.Pointer(&v2))) +} + +func (u *float64ToByteCodec) decode(v1 []byte) float64 { + if len(v1) == 0 { + return 0 + } + + u2 := binary.BigEndian.Uint64(v1) + return *(*float64)(unsafe.Pointer(&u2)) +} + +func (u *complex64ToByteCodec) encode(v2 complex64) []byte { + return binary.BigEndian.AppendUint64(nil, *(*uint64)(unsafe.Pointer(&v2))) +} + +func (u *complex64ToByteCodec) decode(v1 []byte) complex64 { + if len(v1) == 0 { + return 0 + } + + u2 := binary.BigEndian.Uint64(v1) + return *(*complex64)(unsafe.Pointer(&u2)) +} + +func (u *complex128ToByteCodec) encode(v2 complex128) []byte { + r := real(v2) + i := imag(v2) + + bytes := make([]byte, 16) + + binary.BigEndian.PutUint64(bytes[0:], *(*uint64)(unsafe.Pointer(&r))) + binary.BigEndian.PutUint64(bytes[8:], *(*uint64)(unsafe.Pointer(&i))) + + return bytes +} + +func (u *complex128ToByteCodec) decode(v1 []byte) complex128 { + if len(v1) == 0 { + return 0 + } + + r := binary.BigEndian.Uint64(v1[0:]) + i := binary.BigEndian.Uint64(v1[8:]) + + return complex( + *(*float64)(unsafe.Pointer(&r)), + *(*float64)(unsafe.Pointer(&i)), + ) +} diff --git a/kv/ProtoCodec.go b/kv/ProtoCodec.go new file mode 100644 index 0000000..a987b27 --- /dev/null +++ b/kv/ProtoCodec.go @@ -0,0 +1,41 @@ +package kv + +import ( + "github.com/tursom/GoCollections/exceptions" + "github.com/tursom/GoCollections/lang" + "google.golang.org/protobuf/proto" +) + +type ( + protoToByteCodec[V proto.Message] struct { + lang.BaseObject + emptyMessage func() V + } +) + +func ProtoCodec[V proto.Message](emptyMessage func() V) Codec[[]byte, V] { + return &protoToByteCodec[V]{ + emptyMessage: emptyMessage, + } +} + +func ProtoDeCodec[V proto.Message](emptyMessage func() V) Codec[V, []byte] { + return InvertCodec(ProtoCodec(emptyMessage)) +} + +func (p *protoToByteCodec[V]) encode(v2 V) []byte { + bytes, err := proto.Marshal(v2) + if err == nil { + panic(exceptions.Package(err)) + } + + return bytes +} + +func (p *protoToByteCodec[V]) decode(v1 []byte) V { + message := p.emptyMessage() + if err := proto.Unmarshal(v1, message); err != nil { + panic(exceptions.Package(err)) + } + return message +} diff --git a/kv/ReaderCodec.go b/kv/ReaderCodec.go new file mode 100644 index 0000000..1f1f5bb --- /dev/null +++ b/kv/ReaderCodec.go @@ -0,0 +1,91 @@ +package kv + +import ( + "bytes" + "encoding/binary" + "io" + + "github.com/tursom/GoCollections/exceptions" + "github.com/tursom/GoCollections/lang" +) + +// 不推荐使用 + +type ( + readerCodec[V any] struct { + lang.BaseObject + codec Codec[[]byte, V] + } + fixedLengthCodec struct { + lang.BaseObject + frameLength uint32 + } + lengthFieldCodec struct { + lang.BaseObject + } +) + +var ( + LengthFieldCodec Codec[io.Reader, []byte] = &lengthFieldCodec{} +) + +func ReaderCodec[V any](codec Codec[[]byte, V]) Codec[io.Reader, V] { + return &readerCodec[V]{ + codec: codec, + } +} + +func FixedLengthCodec(frameLength uint32) Codec[io.Reader, []byte] { + return &fixedLengthCodec{frameLength: frameLength} +} + +func (r *readerCodec[V]) encode(v2 V) io.Reader { + return bytes.NewReader(r.codec.encode(v2)) +} + +func (r *readerCodec[V]) decode(v1 io.Reader) V { + all, err := io.ReadAll(v1) + if err != nil { + panic(exceptions.Package(err)) + } + + return r.codec.decode(all) +} + +func (f *fixedLengthCodec) encode(v2 []byte) io.Reader { + return bytes.NewReader(v2) +} + +func (f *fixedLengthCodec) decode(v1 io.Reader) []byte { + bs := make([]byte, f.frameLength) + n, err := v1.Read(bs) + if err != nil { + panic(exceptions.Package(err)) + } + + return bs[0:n] +} + +func (l *lengthFieldCodec) encode(v2 []byte) io.Reader { + buffer := bytes.NewBuffer(nil) + + _ = binary.Write(buffer, binary.BigEndian, uint32(len(v2))) + buffer.Write(v2) + + return buffer +} + +func (l *lengthFieldCodec) decode(v1 io.Reader) []byte { + var length uint32 + if err := binary.Read(v1, binary.BigEndian, &length); err != nil { + panic(exceptions.Package(err)) + } + + bs := make([]byte, length) + n, err := v1.Read(bs) + if err != nil { + panic(exceptions.Package(err)) + } + + return bs[0:n] +} diff --git a/kv/StringCodec.go b/kv/StringCodec.go new file mode 100644 index 0000000..420b538 --- /dev/null +++ b/kv/StringCodec.go @@ -0,0 +1,26 @@ +package kv + +import "github.com/tursom/GoCollections/lang" + +type ( + stringToByteCodec struct { + lang.BaseObject + } +) + +var ( + StringToByteCodec Codec[[]byte, string] = &stringToByteCodec{} + ByteToStringCodec = InvertCodec[string, []byte](&stringToByteCodec{}) +) + +func (s *stringToByteCodec) encode(v2 string) []byte { + return []byte(v2) +} + +func (s *stringToByteCodec) decode(v1 []byte) string { + if len(v1) == 0 { + return "" + } + + return string(v1) +} diff --git a/kv/codec.go b/kv/codec.go new file mode 100644 index 0000000..e9999d3 --- /dev/null +++ b/kv/codec.go @@ -0,0 +1,116 @@ +package kv + +import ( + "github.com/tursom/GoCollections/exceptions" + "github.com/tursom/GoCollections/lang" +) + +type ( + Codec[V1, V2 any] interface { + lang.Object + encode(v2 V2) V1 + decode(v1 V1) V2 + } + + codecStore[K1, K2, V1, V2 any] struct { + lang.BaseObject + kvs Store[K1, V1] + kCodec Codec[K1, K2] + vCodec Codec[V1, V2] + } + + kCodecStore[K1, K2, V any] struct { + lang.BaseObject + kvs Store[K1, V] + codec Codec[K1, K2] + } + + vCodecStore[K, V1, V2 any] struct { + lang.BaseObject + kvs Store[K, V1] + codec Codec[V1, V2] + } + + invertCodec[V1, V2 any] struct { + lang.BaseObject + codec Codec[V2, V1] + } +) + +func InvertCodec[V1, V2 any](codec Codec[V2, V1]) Codec[V1, V2] { + return &invertCodec[V1, V2]{codec: codec} +} + +func CodecStore[K1, K2, V1, V2 any]( + kvs Store[K1, V1], + kCodec Codec[K1, K2], + vCodec Codec[V1, V2], +) Store[K2, V2] { + return &codecStore[K1, K2, V1, V2]{ + kvs: kvs, + kCodec: kCodec, + vCodec: vCodec, + } +} + +func KCodecStore[K1, K2, V any]( + kvs Store[K1, V], + codec Codec[K1, K2], +) Store[K2, V] { + return &kCodecStore[K1, K2, V]{ + kvs: kvs, + codec: codec, + } +} + +func VCodecStore[K, V1, V2 any]( + kvs Store[K, V1], + codec Codec[V1, V2], +) Store[K, V2] { + return &vCodecStore[K, V1, V2]{ + kvs: kvs, + codec: codec, + } +} + +func (c *codecStore[K1, K2, V1, V2]) Put(key K2, value V2) exceptions.Exception { + return c.kvs.Put(c.kCodec.encode(key), c.vCodec.encode(value)) +} + +func (c *codecStore[K1, K2, V1, V2]) Get(key K2) (V2, exceptions.Exception) { + value, exception := c.kvs.Get(c.kCodec.encode(key)) + if exception != nil { + return lang.Nil[V2](), exception + } + + return c.vCodec.decode(value), nil +} + +func (c *kCodecStore[K1, K2, V]) Put(key K2, value V) exceptions.Exception { + return c.kvs.Put(c.codec.encode(key), value) +} + +func (c *kCodecStore[K1, K2, V]) Get(key K2) (V, exceptions.Exception) { + return c.kvs.Get(c.codec.encode(key)) +} + +func (c *vCodecStore[K, V1, V2]) Put(key K, value V2) exceptions.Exception { + return c.kvs.Put(key, c.codec.encode(value)) +} + +func (c *vCodecStore[K, V1, V2]) Get(key K) (V2, exceptions.Exception) { + get, exception := c.kvs.Get(key) + if exception != nil { + return lang.Nil[V2](), exception + } + + return c.codec.decode(get), nil +} + +func (i *invertCodec[V1, V2]) encode(v2 V2) V1 { + return i.codec.decode(v2) +} + +func (i *invertCodec[V1, V2]) decode(v1 V1) V2 { + return i.codec.encode(v1) +} diff --git a/kv/kvs.go b/kv/kvs.go new file mode 100644 index 0000000..4111c8e --- /dev/null +++ b/kv/kvs.go @@ -0,0 +1,14 @@ +package kv + +import ( + "github.com/tursom/GoCollections/exceptions" + "github.com/tursom/GoCollections/lang" +) + +type ( + Store[K, V any] interface { + lang.Object + Put(key K, value V) exceptions.Exception + Get(key K) (V, exceptions.Exception) + } +) diff --git a/leveldb/leveldb.go b/leveldb/leveldb.go new file mode 100644 index 0000000..2f0492a --- /dev/null +++ b/leveldb/leveldb.go @@ -0,0 +1,37 @@ +package leveldb + +import ( + "github.com/syndtr/goleveldb/leveldb" + "github.com/tursom/GoCollections/exceptions" + "github.com/tursom/GoCollections/lang" + + "kvs/kv" +) + +type ( + leveldbKVS struct { + lang.BaseObject + db *leveldb.DB + } +) + +func New(db *leveldb.DB) kv.Store[[]byte, []byte] { + return &leveldbKVS{db: db} +} + +func (l *leveldbKVS) Put(key []byte, value []byte) exceptions.Exception { + if err := l.db.Put(key, value, nil); err != nil { + return exceptions.Package(err) + } + + return nil +} + +func (l *leveldbKVS) Get(key []byte) ([]byte, exceptions.Exception) { + value, err := l.db.Get(key, nil) + if err != nil { + return nil, exceptions.Package(err) + } + + return value, nil +} diff --git a/leveldb/leveldb_test.go b/leveldb/leveldb_test.go new file mode 100644 index 0000000..1bbb7b8 --- /dev/null +++ b/leveldb/leveldb_test.go @@ -0,0 +1,27 @@ +package leveldb + +import ( + "testing" + + "github.com/syndtr/goleveldb/leveldb" + + "kvs/kv" +) + +func Test_leveldbKVS(t *testing.T) { + db, err := leveldb.OpenFile("test", nil) + if err != nil { + t.Fatal(err) + } + + s := kv.CodecStore(New(db), kv.StringToByteCodec, kv.StringToByteCodec) + + if err := s.Put("hello", "world!"); err != nil { + t.Fatal(err) + } + + value, exception := s.Get("hello") + if exception != nil || value != "world!" { + t.Fatal(value, exception) + } +} diff --git a/pipe/pipe.go b/pipe/pipe.go new file mode 100644 index 0000000..51fec2a --- /dev/null +++ b/pipe/pipe.go @@ -0,0 +1,11 @@ +package pipe + +import "github.com/tursom/GoCollections/lang" + +type ( + PipelineHandler[V1, V2 any] interface { + lang.Object + encode1(input []V1) []V2 + encode2(input []V2) []V1 + } +) diff --git a/sqlite/sqlite.go b/sqlite/sqlite.go new file mode 100644 index 0000000..d18cf73 --- /dev/null +++ b/sqlite/sqlite.go @@ -0,0 +1,90 @@ +package sqlite + +import ( + "database/sql" + + _ "github.com/mattn/go-sqlite3" + + "github.com/tursom/GoCollections/exceptions" + "github.com/tursom/GoCollections/lang" + + "kvs/kv" +) + +type ( + sqliteKVS struct { + lang.BaseObject + db *sql.DB + table string + } +) + +func New(db *sql.DB, table string) (kv.Store[[]byte, []byte], exceptions.Exception) { + kvs := &sqliteKVS{ + db: db, + table: table, + } + if err := kvs.createTable(); err != nil { + return nil, err + } + + return kvs, nil +} + +func (s *sqliteKVS) createTable() exceptions.Exception { + if _, err := s.db.Exec("create table if not exists " + s.table + " (" + + "k blob primary key not null," + + "v blob" + + ")"); err != nil { + return exceptions.Package(err) + } + + return nil +} + +func (s *sqliteKVS) Get(key []byte) ([]byte, exceptions.Exception) { + rows, err := s.db.Query("select v from "+s.table+" where k = ?", key) + if err != nil { + return nil, exceptions.Package(err) + } + + defer rows.Close() + + if !rows.Next() { + return nil, nil + } + + var data []byte + err = rows.Scan(&data) + if err != nil { + return nil, exceptions.Package(err) + } + + return data, nil +} + +func (s *sqliteKVS) Put(key []byte, value []byte) exceptions.Exception { + if value == nil { + value = []byte{} + } + + exec, err := s.db.Exec("update "+s.table+" set v=? where k=?", value, key) + if err != nil { + return exceptions.Package(err) + } + + affected, err := exec.RowsAffected() + if err != nil { + return exceptions.Package(err) + } + + if affected != 0 { + return nil + } + + if _, err = s.db.Exec("insert into "+s.table+" (k,v) values (?,?)", key, value); err != nil { + return exceptions.Package(err) + } + + return nil +} diff --git a/sqlite/sqlite_test.go b/sqlite/sqlite_test.go new file mode 100644 index 0000000..5416d1f --- /dev/null +++ b/sqlite/sqlite_test.go @@ -0,0 +1,30 @@ +package sqlite + +import ( + "database/sql" + "testing" + + _ "github.com/mattn/go-sqlite3" + "github.com/tursom/GoCollections/exceptions" + + "kvs/kv" +) + +func Test_sqliteKVS(t *testing.T) { + db := exceptions.Exec2r1(sql.Open, "sqlite3", ":memory:") + s, exception := New(db, "kv") + if exception != nil { + t.Fatal(exception) + } + + skvs := kv.CodecStore(s, kv.StringToByteCodec, kv.StringToByteCodec) + + if err := skvs.Put("hello", "world!"); err != nil { + t.Fatal(err) + } + + value, exception := skvs.Get("hello") + if exception != nil || value != "world!" { + t.Fatal(value, exception) + } +}