From b06910f22358637c794ad12411e4abf00a8d819e Mon Sep 17 00:00:00 2001 From: Gregory Eremin Date: Sat, 23 Jun 2018 23:46:35 +0200 Subject: [PATCH] Add sqldb and set packages --- Makefile | 13 ++ set/internal/gen/main.go | 119 +++++++++++++ set/internal/impl/set.go | 74 ++++++++ set/internal/impl/set_test.go | 114 +++++++++++++ set/internal/impl/type.go | 4 + set/setint/set.go | 74 ++++++++ set/setint/set_test.go | 116 +++++++++++++ set/setint16/set.go | 74 ++++++++ set/setint16/set_test.go | 116 +++++++++++++ set/setint32/set.go | 74 ++++++++ set/setint32/set_test.go | 116 +++++++++++++ set/setint64/set.go | 74 ++++++++ set/setint64/set_test.go | 116 +++++++++++++ set/setint8/set.go | 74 ++++++++ set/setint8/set_test.go | 116 +++++++++++++ set/setstring/set.go | 74 ++++++++ set/setstring/set_test.go | 116 +++++++++++++ set/setuint/set.go | 74 ++++++++ set/setuint/set_test.go | 116 +++++++++++++ set/setuint16/set.go | 74 ++++++++ set/setuint16/set_test.go | 116 +++++++++++++ set/setuint32/set.go | 74 ++++++++ set/setuint32/set_test.go | 116 +++++++++++++ set/setuint64/set.go | 74 ++++++++ set/setuint64/set_test.go | 116 +++++++++++++ set/setuint8/set.go | 74 ++++++++ set/setuint8/set_test.go | 116 +++++++++++++ sqldb/conn.go | 106 ++++++++++++ sqldb/result.go | 312 ++++++++++++++++++++++++++++++++++ sqldb/result_test.go | 74 ++++++++ sqldb/sqldb_test.go | 77 +++++++++ 31 files changed, 2983 insertions(+) create mode 100644 Makefile create mode 100644 set/internal/gen/main.go create mode 100644 set/internal/impl/set.go create mode 100644 set/internal/impl/set_test.go create mode 100644 set/internal/impl/type.go create mode 100755 set/setint/set.go create mode 100755 set/setint/set_test.go create mode 100755 set/setint16/set.go create mode 100755 set/setint16/set_test.go create mode 100755 set/setint32/set.go create mode 100755 set/setint32/set_test.go create mode 100755 set/setint64/set.go create mode 100755 set/setint64/set_test.go create mode 100755 set/setint8/set.go create mode 100755 set/setint8/set_test.go create mode 100755 set/setstring/set.go create mode 100755 set/setstring/set_test.go create mode 100755 set/setuint/set.go create mode 100755 set/setuint/set_test.go create mode 100755 set/setuint16/set.go create mode 100755 set/setuint16/set_test.go create mode 100755 set/setuint32/set.go create mode 100755 set/setuint32/set_test.go create mode 100755 set/setuint64/set.go create mode 100755 set/setuint64/set_test.go create mode 100755 set/setuint8/set.go create mode 100755 set/setuint8/set_test.go create mode 100644 sqldb/conn.go create mode 100644 sqldb/result.go create mode 100644 sqldb/result_test.go create mode 100644 sqldb/sqldb_test.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4a39e92 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +i: + @go install ./... + +gen: + @go run set/internal/gen/main.go -tpl=set/internal/impl -dest=set + +test: + @go test ./... + +dbtest: + @go test ./sqldb -dsn="root:@(127.0.0.1:3306)/sqldb_pkg_test" + +fulltest: test dbtest \ No newline at end of file diff --git a/set/internal/gen/main.go b/set/internal/gen/main.go new file mode 100644 index 0000000..ec20220 --- /dev/null +++ b/set/internal/gen/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "reflect" + "strings" +) + +type genericType struct { + name string + testVals []string +} + +var types = []genericType{ + // Int + {name: reflect.Int.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + {name: reflect.Int8.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + {name: reflect.Int16.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + {name: reflect.Int32.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + {name: reflect.Int64.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + // Uint + {name: reflect.Uint.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + {name: reflect.Uint8.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + {name: reflect.Uint16.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + {name: reflect.Uint32.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + {name: reflect.Uint64.String(), testVals: []string{"1", "2", "3", "4", "5"}}, + // String + {name: reflect.String.String(), testVals: []string{`"1"`, `"2"`, `"3"`, `"4"`, `"5"`}}, +} + +func main() { + tplDir := flag.String("tpl", "", "Path to template directory") + destDir := flag.String("dest", "", "Path to destination directory") + flag.Parse() + + if *tplDir == "" { + log.Println("Template directory is not specified") + flag.Usage() + os.Exit(1) + } + if *destDir == "" { + log.Println("Destination directory is not specified") + flag.Usage() + os.Exit(1) + } + + implSource, err := ioutil.ReadFile(path.Join(*tplDir, "set.go")) + if err != nil { + log.Fatalf("Failed to read source file at %s: %v", path.Join(*tplDir, "set.go"), err) + } + + testSource, err := ioutil.ReadFile(path.Join(*tplDir, "set_test.go")) + if err != nil { + log.Fatalf("Failed to read test file at %s: %v", path.Join(*tplDir, "set_test.go"), err) + } + + for _, typ := range types { + log.Printf("Generating package for type %s\n", typ.name) + err := generate(*destDir, implSource, testSource, typ) + if err != nil { + log.Fatalf("Failed to generate a package for type %s: %v", typ.name, err) + } + } + err = gofmt(*destDir) + if err != nil { + log.Fatalf("Formatting failed: %v", err) + } + + log.Println("Set packages were successfully generated") +} + +func generate(destDir string, implSource, testSource []byte, typ genericType) error { + pkgDir := path.Join(destDir, fmt.Sprintf("set%s", typ.name)) + err := os.RemoveAll(pkgDir) + if err != nil && !os.IsNotExist(err) { + return err + } + err = os.Mkdir(pkgDir, 0777) + if err != nil { + return err + } + err = ioutil.WriteFile(path.Join(pkgDir, "set.go"), renderBytes(implSource, typ), 0755) + if err != nil { + return err + } + err = ioutil.WriteFile(path.Join(pkgDir, "set_test.go"), renderBytes(testSource, typ), 0755) + return err +} + +func renderBytes(src []byte, typ genericType) []byte { + return []byte(render(string(src), typ)) +} + +func render(src string, typ genericType) string { + const genericTypeName = "TypeName" + // Replace test constants + src = strings.Replace(src, "One TypeName = 1", "One TypeName = "+typ.testVals[0], 1) + src = strings.Replace(src, "Two TypeName = 2", "Two TypeName = "+typ.testVals[1], 1) + src = strings.Replace(src, "Three TypeName = 3", "Three TypeName = "+typ.testVals[2], 1) + src = strings.Replace(src, "Four TypeName = 4", "Four TypeName = "+typ.testVals[3], 1) + src = strings.Replace(src, "Five TypeName = 5", "Five TypeName = "+typ.testVals[4], 1) + // Replace the type name + src = strings.Replace(src, genericTypeName, typ.name, -1) + return src +} + +func gofmt(dir string) error { + out, err := exec.Command("gofmt", "-w", "-l", dir).CombinedOutput() + if err != nil { + log.Println("gofmt returned:", string(out)) + } + return err +} diff --git a/set/internal/impl/set.go b/set/internal/impl/set.go new file mode 100644 index 0000000..6dd52ef --- /dev/null +++ b/set/internal/impl/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of TypeName. +type Set struct { + items map[TypeName]struct{} +} + +// New creates a new TypeName set. +func New(items ...TypeName) *Set { + s := &Set{items: make(map[TypeName]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...TypeName) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...TypeName) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...TypeName) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []TypeName { + sl := make([]TypeName, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []TypeName { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/internal/impl/set_test.go b/set/internal/impl/set_test.go new file mode 100644 index 0000000..6a94e76 --- /dev/null +++ b/set/internal/impl/set_test.go @@ -0,0 +1,114 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One TypeName = 1 + Two TypeName = 2 + Three TypeName = 3 + Four TypeName = 4 + Five TypeName = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []TypeName{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []TypeName{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []TypeName{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []TypeName{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[TypeName]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []TypeName{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b TypeName) bool { return a < b }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []TypeName{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/internal/impl/type.go b/set/internal/impl/type.go new file mode 100644 index 0000000..967dd99 --- /dev/null +++ b/set/internal/impl/type.go @@ -0,0 +1,4 @@ +package impl + +// TypeName is a type placeholder. +type TypeName int diff --git a/set/setint/set.go b/set/setint/set.go new file mode 100755 index 0000000..853ae9e --- /dev/null +++ b/set/setint/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of int. +type Set struct { + items map[int]struct{} +} + +// New creates a new int set. +func New(items ...int) *Set { + s := &Set{items: make(map[int]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...int) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...int) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...int) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []int { + sl := make([]int, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []int { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setint/set_test.go b/set/setint/set_test.go new file mode 100755 index 0000000..1b2a513 --- /dev/null +++ b/set/setint/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One int = 1 + Two int = 2 + Three int = 3 + Four int = 4 + Five int = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []int{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []int{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []int{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []int{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[int]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []int{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b int) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []int{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setint16/set.go b/set/setint16/set.go new file mode 100755 index 0000000..90d94aa --- /dev/null +++ b/set/setint16/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of int16. +type Set struct { + items map[int16]struct{} +} + +// New creates a new int16 set. +func New(items ...int16) *Set { + s := &Set{items: make(map[int16]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...int16) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...int16) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...int16) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []int16 { + sl := make([]int16, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []int16 { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setint16/set_test.go b/set/setint16/set_test.go new file mode 100755 index 0000000..829be97 --- /dev/null +++ b/set/setint16/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One int16 = 1 + Two int16 = 2 + Three int16 = 3 + Four int16 = 4 + Five int16 = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []int16{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []int16{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []int16{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []int16{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[int16]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []int16{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b int16) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []int16{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setint32/set.go b/set/setint32/set.go new file mode 100755 index 0000000..636a163 --- /dev/null +++ b/set/setint32/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of int32. +type Set struct { + items map[int32]struct{} +} + +// New creates a new int32 set. +func New(items ...int32) *Set { + s := &Set{items: make(map[int32]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...int32) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...int32) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...int32) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []int32 { + sl := make([]int32, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []int32 { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setint32/set_test.go b/set/setint32/set_test.go new file mode 100755 index 0000000..47155eb --- /dev/null +++ b/set/setint32/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One int32 = 1 + Two int32 = 2 + Three int32 = 3 + Four int32 = 4 + Five int32 = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []int32{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []int32{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []int32{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []int32{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[int32]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []int32{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b int32) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []int32{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setint64/set.go b/set/setint64/set.go new file mode 100755 index 0000000..3fb8bbf --- /dev/null +++ b/set/setint64/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of int64. +type Set struct { + items map[int64]struct{} +} + +// New creates a new int64 set. +func New(items ...int64) *Set { + s := &Set{items: make(map[int64]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...int64) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...int64) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...int64) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []int64 { + sl := make([]int64, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []int64 { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setint64/set_test.go b/set/setint64/set_test.go new file mode 100755 index 0000000..576ef80 --- /dev/null +++ b/set/setint64/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One int64 = 1 + Two int64 = 2 + Three int64 = 3 + Four int64 = 4 + Five int64 = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []int64{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []int64{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []int64{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []int64{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[int64]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []int64{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b int64) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []int64{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setint8/set.go b/set/setint8/set.go new file mode 100755 index 0000000..738b633 --- /dev/null +++ b/set/setint8/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of int8. +type Set struct { + items map[int8]struct{} +} + +// New creates a new int8 set. +func New(items ...int8) *Set { + s := &Set{items: make(map[int8]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...int8) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...int8) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...int8) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []int8 { + sl := make([]int8, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []int8 { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setint8/set_test.go b/set/setint8/set_test.go new file mode 100755 index 0000000..def308d --- /dev/null +++ b/set/setint8/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One int8 = 1 + Two int8 = 2 + Three int8 = 3 + Four int8 = 4 + Five int8 = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []int8{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []int8{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []int8{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []int8{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[int8]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []int8{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b int8) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []int8{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setstring/set.go b/set/setstring/set.go new file mode 100755 index 0000000..ca06297 --- /dev/null +++ b/set/setstring/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of string. +type Set struct { + items map[string]struct{} +} + +// New creates a new string set. +func New(items ...string) *Set { + s := &Set{items: make(map[string]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...string) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...string) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...string) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []string { + sl := make([]string, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []string { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setstring/set_test.go b/set/setstring/set_test.go new file mode 100755 index 0000000..59b8b10 --- /dev/null +++ b/set/setstring/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One string = "1" + Two string = "2" + Three string = "3" + Four string = "4" + Five string = "5" +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []string{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []string{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []string{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []string{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[string]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []string{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b string) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []string{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setuint/set.go b/set/setuint/set.go new file mode 100755 index 0000000..c9616e9 --- /dev/null +++ b/set/setuint/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of uint. +type Set struct { + items map[uint]struct{} +} + +// New creates a new uint set. +func New(items ...uint) *Set { + s := &Set{items: make(map[uint]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...uint) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...uint) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...uint) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []uint { + sl := make([]uint, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []uint { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setuint/set_test.go b/set/setuint/set_test.go new file mode 100755 index 0000000..ab36e5b --- /dev/null +++ b/set/setuint/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One uint = 1 + Two uint = 2 + Three uint = 3 + Four uint = 4 + Five uint = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []uint{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []uint{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []uint{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []uint{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[uint]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []uint{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b uint) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []uint{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setuint16/set.go b/set/setuint16/set.go new file mode 100755 index 0000000..fc5faeb --- /dev/null +++ b/set/setuint16/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of uint16. +type Set struct { + items map[uint16]struct{} +} + +// New creates a new uint16 set. +func New(items ...uint16) *Set { + s := &Set{items: make(map[uint16]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...uint16) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...uint16) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...uint16) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []uint16 { + sl := make([]uint16, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []uint16 { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setuint16/set_test.go b/set/setuint16/set_test.go new file mode 100755 index 0000000..21f82ec --- /dev/null +++ b/set/setuint16/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One uint16 = 1 + Two uint16 = 2 + Three uint16 = 3 + Four uint16 = 4 + Five uint16 = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []uint16{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []uint16{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []uint16{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []uint16{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[uint16]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []uint16{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b uint16) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []uint16{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setuint32/set.go b/set/setuint32/set.go new file mode 100755 index 0000000..f240f70 --- /dev/null +++ b/set/setuint32/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of uint32. +type Set struct { + items map[uint32]struct{} +} + +// New creates a new uint32 set. +func New(items ...uint32) *Set { + s := &Set{items: make(map[uint32]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...uint32) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...uint32) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...uint32) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []uint32 { + sl := make([]uint32, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []uint32 { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setuint32/set_test.go b/set/setuint32/set_test.go new file mode 100755 index 0000000..179ffe0 --- /dev/null +++ b/set/setuint32/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One uint32 = 1 + Two uint32 = 2 + Three uint32 = 3 + Four uint32 = 4 + Five uint32 = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []uint32{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []uint32{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []uint32{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []uint32{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[uint32]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []uint32{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b uint32) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []uint32{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setuint64/set.go b/set/setuint64/set.go new file mode 100755 index 0000000..a8a45f8 --- /dev/null +++ b/set/setuint64/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of uint64. +type Set struct { + items map[uint64]struct{} +} + +// New creates a new uint64 set. +func New(items ...uint64) *Set { + s := &Set{items: make(map[uint64]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...uint64) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...uint64) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...uint64) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []uint64 { + sl := make([]uint64, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []uint64 { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setuint64/set_test.go b/set/setuint64/set_test.go new file mode 100755 index 0000000..5e0cf60 --- /dev/null +++ b/set/setuint64/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One uint64 = 1 + Two uint64 = 2 + Three uint64 = 3 + Four uint64 = 4 + Five uint64 = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []uint64{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []uint64{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []uint64{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []uint64{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[uint64]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []uint64{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b uint64) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []uint64{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/set/setuint8/set.go b/set/setuint8/set.go new file mode 100755 index 0000000..4fcd10b --- /dev/null +++ b/set/setuint8/set.go @@ -0,0 +1,74 @@ +package impl + +import ( + "fmt" + "sort" +) + +// Set is a set of uint8. +type Set struct { + items map[uint8]struct{} +} + +// New creates a new uint8 set. +func New(items ...uint8) *Set { + s := &Set{items: make(map[uint8]struct{}, len(items))} + s.Add(items...) + return s +} + +// Add adds given items to the set. +func (s *Set) Add(items ...uint8) *Set { + for _, item := range items { + s.items[item] = struct{}{} + } + return s +} + +// Remove delete given items from the set. +func (s *Set) Remove(items ...uint8) *Set { + for _, item := range items { + delete(s.items, item) + } + return s +} + +// Has returns true if all the given items are included in the set. +func (s *Set) Has(items ...uint8) bool { + for _, item := range items { + if _, ok := s.items[item]; !ok { + return false + } + } + return true +} + +// Len returns the size of the set. +func (s *Set) Len() int { + return len(s.items) +} + +// Slice returns items of the set as a slice. +func (s *Set) Slice() []uint8 { + sl := make([]uint8, len(s.items)) + i := 0 + for item := range s.items { + sl[i] = item + i++ + } + return sl +} + +// SortedSlice returns items of the set as a slice sorted ascending. +func (s *Set) SortedSlice() []uint8 { + ss := s.Slice() + sort.Slice(ss, func(i, j int) bool { + return ss[i] < ss[j] + }) + return ss +} + +// String implements fmt.Stringer interface. +func (s *Set) String() string { + return fmt.Sprintf("[%v]", s.Slice()) +} diff --git a/set/setuint8/set_test.go b/set/setuint8/set_test.go new file mode 100755 index 0000000..1142eb9 --- /dev/null +++ b/set/setuint8/set_test.go @@ -0,0 +1,116 @@ +package impl + +import ( + "testing" + + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/google/go-cmp/cmp" +) + +const ( + One uint8 = 1 + Two uint8 = 2 + Three uint8 = 3 + Four uint8 = 4 + Five uint8 = 5 +) + +func TestNew(t *testing.T) { + s := New(One, Two, Three) + if s == nil { + t.Fatal("Set is nil") + } + if s.Len() != 3 { + t.Errorf("Expected set to contain 3 items, got %d", s.Len()) + } + for _, item := range []uint8{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestAdd(t *testing.T) { + s := New() + if s.Len() != 0 { + t.Errorf("Expected set to be empty, got %d items", s.Len()) + } + s.Add(One) + s.Add(Two, Three) + for _, item := range []uint8{One, Two, Three} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestRemove(t *testing.T) { + s := New(One, Two, Three, Four, Five) + s.Remove(One, Two) + s.Remove(Three) + if s.Len() != 2 { + t.Errorf("Expected set to contain 2 items, got %d", s.Len()) + } + for _, item := range []uint8{One, Two, Three} { + if ok := s.Has(item); ok { + t.Errorf("Set is expected to not contain item %q", item) + } + } + for _, item := range []uint8{Four, Five} { + if ok := s.Has(item); !ok { + t.Errorf("Set is expected to contain item %q", item) + } + } +} + +func TestHas(t *testing.T) { + s := New(One, Two) + table := map[uint8]bool{ + One: true, + Two: true, + Three: false, + Four: false, + Five: false, + } + for v, exp := range table { + if res := s.Has(v); res != exp { + t.Errorf("Item: %v, In: %v, Expected: %v", v, res, exp) + } + } +} + +func TestLen(t *testing.T) { + table := map[*Set]int{ + New(): 0, + New(One): 1, + New(Two, Three): 2, + New(One, Two, Three, Four, Five, Five): 5, + } + for s, exp := range table { + if res := s.Len(); res != exp { + t.Errorf("Expected set %s to have length %d, got %d", s, exp, res) + } + } +} + +func TestSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.Slice() + exp := []uint8{One, Two, Three} + ignoreOrder := cmpopts.SortSlices(func(a, b uint8) bool { + return a < b + }) + if !cmp.Equal(exp, out, ignoreOrder) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} + +func TestSortedSlice(t *testing.T) { + s := New(One, Two, Three) + out := s.SortedSlice() + exp := []uint8{One, Two, Three} + if !cmp.Equal(exp, out) { + t.Errorf("Retured slice does not match: %s", cmp.Diff(exp, out)) + } +} diff --git a/sqldb/conn.go b/sqldb/conn.go new file mode 100644 index 0000000..50cc13f --- /dev/null +++ b/sqldb/conn.go @@ -0,0 +1,106 @@ +package sqldb + +import ( + "context" + "database/sql" + "time" + + "github.com/juju/errors" +) + +// Conn represents database connection. +type Conn struct { + db *sql.DB + + beforeCallbacks []BeforeCallback + afterCallbacks []AfterCallback +} + +type ( + // BeforeCallback is a kind of function that can be called before a query is + // executed. + BeforeCallback func(ctx context.Context, query string) + // AfterCallback is a kind of function that can be called after a query was + // executed. + AfterCallback func(ctx context.Context, query string, took time.Duration, err error) +) + +// Flavor defines a kind of SQL database. +type Flavor string + +const ( + // MySQL is the MySQL SQL flavor. + MySQL Flavor = "mysql" + // PostgreSQL is the PostgreSQL SQL flavor. + PostgreSQL Flavor = "postgresql" +) + +// Connect establishes a new database connection. +func Connect(ctx context.Context, f Flavor, dsn string) (*Conn, error) { + conn, err := sql.Open(string(f), dsn) + if err != nil { + return nil, errors.Annotate(err, "Failed to establish connection") + } + err = conn.PingContext(ctx) + if err != nil { + return nil, errors.Annotate(err, "Connection is not responding") + } + return &Conn{db: conn}, nil +} + +// Exec executes a query and does not expect any result. +func (c *Conn) Exec(ctx context.Context, query string, args ...interface{}) Result { + c.callBefore(ctx, query) + startedAt := time.Now() + res, err := c.db.ExecContext(ctx, query, args...) + c.callAfter(ctx, query, time.Since(startedAt), err) + if err != nil { + return result{err: err} + } + return result{res: res} +} + +// Query executes a query and returns a result object that can later be used to +// retrieve values. +func (c *Conn) Query(ctx context.Context, query string, args ...interface{}) Result { + var r result + c.callBefore(ctx, query) + startedAt := time.Now() + r.rows, r.err = c.db.QueryContext(ctx, query, args...) + c.callAfter(ctx, query, time.Since(startedAt), r.err) + return r +} + +// DB returns the underlying DB object. +func (c *Conn) DB() *sql.DB { + return c.db +} + +// Close closes the connection. +func (c *Conn) Close() error { + return c.db.Close() +} + +// Before adds a callback function that would be called before a query is +// executed. +func (c *Conn) Before(cb BeforeCallback) { + c.beforeCallbacks = append(c.beforeCallbacks, cb) +} + +// After adds a callback function that would be called after a query was +// executed. +func (c *Conn) After(cb AfterCallback) { + c.afterCallbacks = append(c.afterCallbacks, cb) +} + +func (c *Conn) callBefore(ctx context.Context, query string) { + for _, cb := range c.beforeCallbacks { + cb(ctx, query) + } +} + +func (c *Conn) callAfter(ctx context.Context, query string, took time.Duration, err error) { + for _, cb := range c.afterCallbacks { + cb(ctx, query, took, err) + } +} diff --git a/sqldb/result.go b/sqldb/result.go new file mode 100644 index 0000000..b222cf9 --- /dev/null +++ b/sqldb/result.go @@ -0,0 +1,312 @@ +package sqldb + +import ( + "database/sql" + "fmt" + "reflect" +) + +// Result represents query result. +type Result interface { + // Load decodes rows into provided variable. + Load(dest interface{}) Result + // Error returns an error if one happened during query execution. + Error() error + // Rows returns original database rows object. + Rows() *sql.Rows + // LastInsertID returns the last inserted record ID for results obtained + // from Exec calls. + LastInsertID() int64 + // RowsAffected returns the number of rows affected for results obtained + // from Exec calls. + RowsAffected() int64 +} + +type result struct { + rows *sql.Rows + res sql.Result + err error +} + +func (r result) Rows() *sql.Rows { + return r.rows +} + +func (r result) Error() error { + return r.err +} + +func (r result) LastInsertID() int64 { + if r.res == nil { + return 0 + } + id, err := r.res.LastInsertId() + if err != nil { + return 0 + } + return id +} + +func (r result) RowsAffected() int64 { + if r.res == nil { + return 0 + } + ra, err := r.res.RowsAffected() + if err != nil { + return 0 + } + return ra +} + +func (r result) Load(dest interface{}) Result { + if r.err != nil { + return r + } + defer r.rows.Close() + + dtyp := reflect.TypeOf(dest) + if dtyp.Kind() != reflect.Ptr { + panic("Value must be a pointer") + } + dtyp = dtyp.Elem() + + switch dtyp.Kind() { + case reflect.Struct: + r.loadStruct(dtyp, dest) + case reflect.Map: + r.loadMap(dest.(*map[string]interface{})) + case reflect.Slice: + switch dtyp.Elem().Kind() { + case reflect.Struct: + r.loadSliceOfStructs(dtyp, dest) + case reflect.Map: + r.loadSliceOfMaps(dest.(*[]map[string]interface{})) + default: + r.loadSlice(dtyp, dest) + } + default: + r.loadValue(dest) + } + + if r.err == nil && r.rows.Err() != nil { + return r.withError(r.rows.Err()) + } + + return r +} + +func (r *result) loadValue(dest interface{}) { + if r.rows.Next() { + r.err = r.rows.Scan(dest) + } +} + +func (r *result) loadSlice(typ reflect.Type, dest interface{}) { + vSlice := reflect.MakeSlice(typ, 0, 0) + for r.rows.Next() { + val := reflect.New(typ.Elem()) + r.err = r.rows.Scan(val.Interface()) + if r.err != nil { + return + } + vSlice = reflect.Append(vSlice, val.Elem()) + } + reflect.ValueOf(dest).Elem().Set(vSlice) +} + +func (r *result) loadMap(dest *map[string]interface{}) { + if !r.rows.Next() { + return + } + + cols, err := r.rows.Columns() + if err != nil { + r.err = err + return + } + colTypes, err := r.rows.ColumnTypes() + if err != nil { + r.err = err + return + } + + vals := make([]interface{}, len(cols)) + for i := range cols { + vals[i] = newValue(colTypes[i]) + } + err = r.rows.Scan(vals...) + if err != nil { + r.err = err + return + } + + if *dest == nil { + *dest = make(map[string]interface{}, len(cols)) + } + for i, col := range cols { + switch tval := vals[i].(type) { + case *int64: + (*dest)[col] = *tval + case *string: + (*dest)[col] = *tval + case *bool: + (*dest)[col] = *tval + } + } +} + +func (r *result) loadSliceOfMaps(dest *[]map[string]interface{}) { + cols, err := r.rows.Columns() + if err != nil { + r.err = err + return + } + colTypes, err := r.rows.ColumnTypes() + if err != nil { + r.err = err + return + } + + if *dest == nil { + *dest = make([]map[string]interface{}, 0) + } + for r.rows.Next() { + vals := make([]interface{}, len(cols)) + for i := range cols { + vals[i] = newValue(colTypes[i]) + } + err = r.rows.Scan(vals...) + if err != nil { + r.err = err + return + } + + row := make(map[string]interface{}, len(cols)) + for i, col := range cols { + switch tval := vals[i].(type) { + case *int64: + row[col] = *tval + case *string: + row[col] = *tval + case *bool: + row[col] = *tval + } + } + *dest = append(*dest, row) + } +} + +func (r *result) loadStruct(typ reflect.Type, dest interface{}) { + if !r.rows.Next() { + return + } + + cols, err := r.rows.Columns() + if err != nil { + r.err = err + return + } + + val := reflect.ValueOf(dest).Elem() + vals := make([]interface{}, len(cols)) + tm := tagMap(cols, val.Type()) + for i := range cols { + if fi, ok := tm[i]; ok { + fval := val.Field(fi) + vals[i] = reflect.New(fval.Type()).Interface() + } else { + var dummy interface{} + vals[i] = &dummy + } + } + + err = r.rows.Scan(vals...) + if err != nil { + r.err = err + return + } + + for i, fi := range tm { + fval := val.Field(fi) + fval.Set(reflect.ValueOf(vals[i]).Elem()) + } +} + +func (r *result) loadSliceOfStructs(typ reflect.Type, dest interface{}) { + cols, err := r.rows.Columns() + if err != nil { + r.err = err + return + } + + vSlice := reflect.ValueOf(dest).Elem() + tSlice := vSlice.Type() + tElem := tSlice.Elem() + tm := tagMap(cols, tElem) + + for r.rows.Next() { + vals := make([]interface{}, len(cols)) + val := reflect.New(tElem).Elem() + for i := range cols { + if fi, ok := tm[i]; ok { + fval := val.Field(fi) + vals[i] = reflect.New(fval.Type()).Interface() + } else { + var dummy interface{} + vals[i] = &dummy + } + } + + err = r.rows.Scan(vals...) + if err != nil { + r.err = err + return + } + + for i, fi := range tm { + fval := val.Field(fi) + fval.Set(reflect.ValueOf(vals[i]).Elem()) + } + vSlice.Set(reflect.Append(vSlice, val)) + } +} + +func (r result) withError(err error) Result { + r.err = err + return r +} + +func newValue(typ *sql.ColumnType) interface{} { + switch typ.DatabaseTypeName() { + case "VARCHAR", "NVARCHAR", "TEXT": + var s string + return &s + case "INT", "BIGINT": + var i int64 + return &i + case "BOOL": + var b bool + return &b + default: + panic(fmt.Errorf("Unsupported MySQL type: %s", typ.DatabaseTypeName())) + } +} + +func tagMap(cols []string, typ reflect.Type) map[int]int { + fieldIndices := map[string]int{} + for i := 0; i < typ.NumField(); i++ { + tag := typ.Field(i).Tag.Get("db") + if tag != "" { + fieldIndices[tag] = i + } + } + + colFields := map[int]int{} + for i, col := range cols { + if fi, ok := fieldIndices[col]; ok { + colFields[i] = fi + } + } + + return colFields +} diff --git a/sqldb/result_test.go b/sqldb/result_test.go new file mode 100644 index 0000000..48f1ee4 --- /dev/null +++ b/sqldb/result_test.go @@ -0,0 +1,74 @@ +package sqldb + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestLoadSingleValue(t *testing.T) { + ctx := context.Background() + exp := int(1) + var out int + mustT(t, conn.Query(ctx, "SELECT 1").Load(&out)) + if exp != out { + t.Errorf("Value doesn't match: expected %d, got %d", exp, out) + } +} + +func TestLoadSlice(t *testing.T) { + ctx := context.Background() + exp := []int{1, 2} + var out []int + mustT(t, conn.Query(ctx, "SELECT id FROM sqldb_test").Load(&out)) + if !cmp.Equal(exp, out) { + t.Errorf("Values dont't match: %s", cmp.Diff(exp, out)) + } +} + +func TestLoadMap(t *testing.T) { + ctx := context.Background() + exp := map[string]interface{}{"id": int64(1), "name": "Alice"} + var out map[string]interface{} + mustT(t, conn.Query(ctx, "SELECT * FROM sqldb_test WHERE id = 1").Load(&out)) + if !cmp.Equal(exp, out) { + t.Errorf("Record doesn't match: %s", cmp.Diff(exp, out)) + } +} + +func TestLoadSliceOfMaps(t *testing.T) { + ctx := context.Background() + exp := []map[string]interface{}{ + {"id": int64(1), "name": "Alice"}, + {"id": int64(2), "name": "Bob"}, + } + var out []map[string]interface{} + mustT(t, conn.Query(ctx, "SELECT * FROM sqldb_test ORDER BY id ASC").Load(&out)) + if !cmp.Equal(exp, out) { + t.Errorf("Records don't match: %s", cmp.Diff(exp, out)) + } +} + +func TestLoadStruct(t *testing.T) { + ctx := context.Background() + exp := record{ID: 1, Name: "Alice"} + var out record + mustT(t, conn.Query(ctx, "SELECT * FROM sqldb_test WHERE id = 1").Load(&out)) + if !cmp.Equal(exp, out) { + t.Errorf("Record doesn't match: %s", cmp.Diff(exp, out)) + } +} + +func TestLoadSliceOfStructs(t *testing.T) { + ctx := context.Background() + exp := []record{ + {ID: 1, Name: "Alice"}, + {ID: 2, Name: "Bob"}, + } + var out []record + mustT(t, conn.Query(ctx, "SELECT * FROM sqldb_test").Load(&out)) + if !cmp.Equal(exp, out) { + t.Errorf("Records don't match: %s", cmp.Diff(exp, out)) + } +} diff --git a/sqldb/sqldb_test.go b/sqldb/sqldb_test.go new file mode 100644 index 0000000..9aae7c2 --- /dev/null +++ b/sqldb/sqldb_test.go @@ -0,0 +1,77 @@ +package sqldb + +import ( + "context" + "flag" + "fmt" + "log" + "os" + "testing" + + _ "github.com/go-sql-driver/mysql" // MySQL driver +) + +type record struct { + ID uint `db:"id"` + Name string `db:"name"` +} + +var conn *Conn + +func TestMain(m *testing.M) { + dsn := flag.String("dsn", "", "Database source name") + flag.Parse() + if *dsn == "" { + log.Println("Database source name is not provided, skipping package tests") + os.Exit(0) + } + + log.Println("Establishing connection to the test database") + ctx := context.Background() + var err error + conn, err = Connect(ctx, MySQL, *dsn) + if err != nil { + log.Fatalf("Failed to connect: %v\n", err) + } + + log.Println("Seeding database") + must(conn.Exec(ctx, ` + DROP TABLE IF EXISTS sqldb_test + `)) + must(conn.Exec(ctx, ` + CREATE TABLE sqldb_test ( + id int(11) UNSIGNED NOT NULL, + name VARCHAR(10) DEFAULT '', + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=ascii + `)) + must(conn.Exec(ctx, ` + INSERT INTO sqldb_test (id, name) + VALUES + (1, "Alice"), + (2, "Bob") + `)) + + fmt.Println("Starting test suite") + exitCode := m.Run() + log.Println("Test suite finished") + if err := conn.Close(); err != nil { + log.Printf("Failed to close connection: %v\n", err) + } + os.Exit(exitCode) +} + +func mustT(t *testing.T, r Result) Result { + t.Helper() + if r.Error() != nil { + t.Fatalf("Query failed: %v", r.Error()) + } + return r +} + +func must(r Result) Result { + if r.Error() != nil { + log.Fatalf("Query failed: %v\n", r.Error()) + } + return r +}