good morning!!!!

Skip to content
Snippets Groups Projects
Commit 2b32d62f authored by José Nieto's avatar José Nieto
Browse files

remove hashstructure package and refactor cache mechanism

parent 82a17710
Branches
Tags
No related merge requests found
Showing
with 139 additions and 1011 deletions
...@@ -11,6 +11,8 @@ require ( ...@@ -11,6 +11,8 @@ require (
github.com/jackc/pgx/v4 v4.15.0 github.com/jackc/pgx/v4 v4.15.0
github.com/lib/pq v1.10.4 github.com/lib/pq v1.10.4
github.com/mattn/go-sqlite3 v1.14.9 github.com/mattn/go-sqlite3 v1.14.9
github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/segmentio/fasthash v1.0.3
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect
......
...@@ -100,6 +100,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd ...@@ -100,6 +100,8 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
...@@ -112,6 +114,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= ...@@ -112,6 +114,8 @@ github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
......
...@@ -24,25 +24,21 @@ package cache ...@@ -24,25 +24,21 @@ package cache
import ( import (
"container/list" "container/list"
"errors" "errors"
"fmt"
"strconv"
"sync" "sync"
"github.com/upper/db/v4/internal/cache/hashstructure"
) )
const defaultCapacity = 128 const defaultCapacity = 128
// Cache holds a map of volatile key -> values. // Cache holds a map of volatile key -> values.
type Cache struct { type Cache struct {
cache map[string]*list.Element keys *list.List
li *list.List items map[uint64]*list.Element
capacity int
mu sync.RWMutex mu sync.RWMutex
capacity int
} }
type item struct { type cacheItem struct {
key string key uint64
value interface{} value interface{}
} }
...@@ -52,11 +48,11 @@ func NewCacheWithCapacity(capacity int) (*Cache, error) { ...@@ -52,11 +48,11 @@ func NewCacheWithCapacity(capacity int) (*Cache, error) {
if capacity < 1 { if capacity < 1 {
return nil, errors.New("Capacity must be greater than zero.") return nil, errors.New("Capacity must be greater than zero.")
} }
return &Cache{ c := &Cache{
cache: make(map[string]*list.Element),
li: list.New(),
capacity: capacity, capacity: capacity,
}, nil }
c.init()
return c, nil
} }
// NewCache initializes a new caching space with default settings. // NewCache initializes a new caching space with default settings.
...@@ -68,6 +64,11 @@ func NewCache() *Cache { ...@@ -68,6 +64,11 @@ func NewCache() *Cache {
return c return c
} }
func (c *Cache) init() {
c.items = make(map[uint64]*list.Element)
c.keys = list.New()
}
// Read attempts to retrieve a cached value as a string, if the value does not // Read attempts to retrieve a cached value as a string, if the value does not
// exists returns an empty string and false. // exists returns an empty string and false.
func (c *Cache) Read(h Hashable) (string, bool) { func (c *Cache) Read(h Hashable) (string, bool) {
...@@ -84,33 +85,35 @@ func (c *Cache) Read(h Hashable) (string, bool) { ...@@ -84,33 +85,35 @@ func (c *Cache) Read(h Hashable) (string, bool) {
func (c *Cache) ReadRaw(h Hashable) (interface{}, bool) { func (c *Cache) ReadRaw(h Hashable) (interface{}, bool) {
c.mu.RLock() c.mu.RLock()
defer c.mu.RUnlock() defer c.mu.RUnlock()
data, ok := c.cache[h.Hash()]
item, ok := c.items[h.Hash()]
if ok { if ok {
return data.Value.(*item).value, true return item.Value.(*cacheItem).value, true
} }
return nil, false return nil, false
} }
// Write stores a value in memory. If the value already exists its overwritten. // Write stores a value in memory. If the value already exists its overwritten.
func (c *Cache) Write(h Hashable, value interface{}) { func (c *Cache) Write(h Hashable, value interface{}) {
key := h.Hash()
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if el, ok := c.cache[key]; ok { key := h.Hash()
el.Value.(*item).value = value
c.li.MoveToFront(el) if item, ok := c.items[key]; ok {
item.Value.(*cacheItem).value = value
c.keys.MoveToFront(item)
return return
} }
c.cache[key] = c.li.PushFront(&item{key, value}) c.items[key] = c.keys.PushFront(&cacheItem{key, value})
for c.li.Len() > c.capacity { for c.keys.Len() > c.capacity {
el := c.li.Remove(c.li.Back()) item := c.keys.Remove(c.keys.Back()).(*cacheItem)
delete(c.cache, el.(*item).key) delete(c.items, item.key)
if p, ok := el.(*item).value.(HasOnPurge); ok { if p, ok := item.value.(HasOnEvict); ok {
p.OnPurge() p.OnEvict()
} }
} }
} }
...@@ -120,33 +123,12 @@ func (c *Cache) Write(h Hashable, value interface{}) { ...@@ -120,33 +123,12 @@ func (c *Cache) Write(h Hashable, value interface{}) {
func (c *Cache) Clear() { func (c *Cache) Clear() {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
for _, el := range c.cache {
if p, ok := el.Value.(*item).value.(HasOnPurge); ok {
p.OnPurge()
}
}
c.cache = make(map[string]*list.Element)
c.li.Init()
}
// Hash returns a hash of the given struct. for _, item := range c.items {
func Hash(v interface{}) string { if p, ok := item.Value.(*cacheItem).value.(HasOnEvict); ok {
q, err := hashstructure.Hash(v, nil) p.OnEvict()
if err != nil {
panic(fmt.Sprintf("Could not hash struct: %v", err.Error()))
}
return strconv.FormatUint(q, 10)
} }
type hash struct {
name string
}
func (h *hash) Hash() string {
return h.name
} }
// String returns a Hashable that produces a hash equal to the given string. c.init()
func String(s string) Hashable {
return &hash{s}
} }
...@@ -23,6 +23,7 @@ package cache ...@@ -23,6 +23,7 @@ package cache
import ( import (
"fmt" "fmt"
"hash/fnv"
"testing" "testing"
) )
...@@ -32,8 +33,10 @@ type cacheableT struct { ...@@ -32,8 +33,10 @@ type cacheableT struct {
Name string Name string
} }
func (ct *cacheableT) Hash() string { func (ct *cacheableT) Hash() uint64 {
return Hash(ct) s := fnv.New64()
s.Sum([]byte(ct.Name))
return s.Sum64()
} }
var ( var (
...@@ -77,6 +80,13 @@ func BenchmarkNewCache(b *testing.B) { ...@@ -77,6 +80,13 @@ func BenchmarkNewCache(b *testing.B) {
} }
} }
func BenchmarkNewCacheAndClear(b *testing.B) {
for i := 0; i < b.N; i++ {
c := NewCache()
c.Clear()
}
}
func BenchmarkReadNonExistentValue(b *testing.B) { func BenchmarkReadNonExistentValue(b *testing.B) {
z := NewCache() z := NewCache()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
......
The MIT License (MIT)
Copyright (c) 2016 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# hashstructure
hashstructure is a Go library for creating a unique hash value
for arbitrary values in Go.
This can be used to key values in a hash (for use in a map, set, etc.)
that are complex. The most common use case is comparing two values without
sending data across the network, caching values locally (de-dup), and so on.
## Features
* Hash any arbitrary Go value, including complex types.
* Tag a struct field to ignore it and not affect the hash value.
* Tag a slice type struct field to treat it as a set where ordering
doesn't affect the hash code but the field itself is still taken into
account to create the hash value.
* Optionally specify a custom hash function to optimize for speed, collision
avoidance for your data set, etc.
## Installation
Standard `go get`:
```
$ go get github.com/mitchellh/hashstructure
```
## Usage & Example
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/hashstructure).
A quick code example is shown below:
type ComplexStruct struct {
Name string
Age uint
Metadata map[string]interface{}
}
v := ComplexStruct{
Name: "mitchellh",
Age: 64,
Metadata: map[string]interface{}{
"car": true,
"location": "California",
"siblings": []string{"Bob", "John"},
},
}
hash, err := hashstructure.Hash(v, nil)
if err != nil {
panic(err)
}
fmt.Printf("%d", hash)
// Output:
// 2307517237273902113
package hashstructure
import (
"encoding/binary"
"fmt"
"hash"
"hash/fnv"
"reflect"
)
// HashOptions are options that are available for hashing.
type HashOptions struct {
// Hasher is the hash function to use. If this isn't set, it will
// default to FNV.
Hasher hash.Hash64
// TagName is the struct tag to look at when hashing the structure.
// By default this is "hash".
TagName string
}
// Hash returns the hash value of an arbitrary value.
//
// If opts is nil, then default options will be used. See HashOptions
// for the default values.
//
// Notes on the value:
//
// * Unexported fields on structs are ignored and do not affect the
// hash value.
//
// * Adding an exported field to a struct with the zero value will change
// the hash value.
//
// For structs, the hashing can be controlled using tags. For example:
//
// struct {
// Name string
// UUID string `hash:"ignore"`
// }
//
// The available tag values are:
//
// * "ignore" - The field will be ignored and not affect the hash code.
//
// * "set" - The field will be treated as a set, where ordering doesn't
// affect the hash code. This only works for slices.
//
func Hash(v interface{}, opts *HashOptions) (uint64, error) {
// Create default options
if opts == nil {
opts = &HashOptions{}
}
if opts.Hasher == nil {
opts.Hasher = fnv.New64()
}
if opts.TagName == "" {
opts.TagName = "hash"
}
// Reset the hash
opts.Hasher.Reset()
// Create our walker and walk the structure
w := &walker{
h: opts.Hasher,
tag: opts.TagName,
}
return w.visit(reflect.ValueOf(v), nil)
}
type walker struct {
h hash.Hash64
tag string
}
type visitOpts struct {
// Flags are a bitmask of flags to affect behavior of this visit
Flags visitFlag
// Information about the struct containing this field
Struct interface{}
StructField string
}
func (w *walker) visit(v reflect.Value, opts *visitOpts) (uint64, error) {
// Loop since these can be wrapped in multiple layers of pointers
// and interfaces.
for {
// If we have an interface, dereference it. We have to do this up
// here because it might be a nil in there and the check below must
// catch that.
if v.Kind() == reflect.Interface {
v = v.Elem()
continue
}
if v.Kind() == reflect.Ptr {
v = reflect.Indirect(v)
continue
}
break
}
// If it is nil, treat it like a zero.
if !v.IsValid() {
var tmp int8
v = reflect.ValueOf(tmp)
}
// Binary writing can use raw ints, we have to convert to
// a sized-int, we'll choose the largest...
switch v.Kind() {
case reflect.Int:
v = reflect.ValueOf(int64(v.Int()))
case reflect.Uint:
v = reflect.ValueOf(uint64(v.Uint()))
case reflect.Bool:
var tmp int8
if v.Bool() {
tmp = 1
}
v = reflect.ValueOf(tmp)
}
k := v.Kind()
// We can shortcut numeric values by directly binary writing them
if k >= reflect.Int && k <= reflect.Complex64 {
// A direct hash calculation
w.h.Reset()
err := binary.Write(w.h, binary.LittleEndian, v.Interface())
return w.h.Sum64(), err
}
switch k {
case reflect.Array:
var h uint64
l := v.Len()
for i := 0; i < l; i++ {
current, err := w.visit(v.Index(i), nil)
if err != nil {
return 0, err
}
h = hashUpdateOrdered(w.h, h, current)
}
return h, nil
case reflect.Map:
var includeMap IncludableMap
if opts != nil && opts.Struct != nil {
if v, ok := opts.Struct.(IncludableMap); ok {
includeMap = v
}
}
// Build the hash for the map. We do this by XOR-ing all the key
// and value hashes. This makes it deterministic despite ordering.
var h uint64
for _, k := range v.MapKeys() {
v := v.MapIndex(k)
if includeMap != nil {
incl, err := includeMap.HashIncludeMap(
opts.StructField, k.Interface(), v.Interface())
if err != nil {
return 0, err
}
if !incl {
continue
}
}
kh, err := w.visit(k, nil)
if err != nil {
return 0, err
}
vh, err := w.visit(v, nil)
if err != nil {
return 0, err
}
fieldHash := hashUpdateOrdered(w.h, kh, vh)
h = hashUpdateUnordered(h, fieldHash)
}
return h, nil
case reflect.Struct:
var include Includable
parent := v.Interface()
if impl, ok := parent.(Includable); ok {
include = impl
}
t := v.Type()
h, err := w.visit(reflect.ValueOf(t.Name()), nil)
if err != nil {
return 0, err
}
l := v.NumField()
for i := 0; i < l; i++ {
if v := v.Field(i); v.CanSet() || t.Field(i).Name != "_" {
var f visitFlag
fieldType := t.Field(i)
if fieldType.PkgPath != "" {
// Unexported
continue
}
tag := fieldType.Tag.Get(w.tag)
if tag == "ignore" {
// Ignore this field
continue
}
// Check if we implement includable and check it
if include != nil {
incl, err := include.HashInclude(fieldType.Name, v)
if err != nil {
return 0, err
}
if !incl {
continue
}
}
switch tag {
case "set":
f |= visitFlagSet
}
kh, err := w.visit(reflect.ValueOf(fieldType.Name), nil)
if err != nil {
return 0, err
}
vh, err := w.visit(v, &visitOpts{
Flags: f,
Struct: parent,
StructField: fieldType.Name,
})
if err != nil {
return 0, err
}
fieldHash := hashUpdateOrdered(w.h, kh, vh)
h = hashUpdateUnordered(h, fieldHash)
}
}
return h, nil
case reflect.Slice:
// We have two behaviors here. If it isn't a set, then we just
// visit all the elements. If it is a set, then we do a deterministic
// hash code.
var h uint64
var set bool
if opts != nil {
set = (opts.Flags & visitFlagSet) != 0
}
l := v.Len()
for i := 0; i < l; i++ {
current, err := w.visit(v.Index(i), nil)
if err != nil {
return 0, err
}
if set {
h = hashUpdateUnordered(h, current)
} else {
h = hashUpdateOrdered(w.h, h, current)
}
}
return h, nil
case reflect.String:
// Directly hash
w.h.Reset()
_, err := w.h.Write([]byte(v.String()))
return w.h.Sum64(), err
default:
return 0, fmt.Errorf("unknown kind to hash: %s", k)
}
}
func hashUpdateOrdered(h hash.Hash64, a, b uint64) uint64 {
// For ordered updates, use a real hash function
h.Reset()
// We just panic if the binary writes fail because we are writing
// an int64 which should never be fail-able.
e1 := binary.Write(h, binary.LittleEndian, a)
e2 := binary.Write(h, binary.LittleEndian, b)
if e1 != nil {
panic(e1)
}
if e2 != nil {
panic(e2)
}
return h.Sum64()
}
func hashUpdateUnordered(a, b uint64) uint64 {
return a ^ b
}
// visitFlag is used as a bitmask for affecting visit behavior
type visitFlag uint
const (
visitFlagInvalid visitFlag = iota
visitFlagSet = iota << 1
)
var (
_ = visitFlagInvalid
)
package hashstructure
import (
"testing"
)
func TestHash_identity(t *testing.T) {
cases := []interface{}{
nil,
"foo",
42,
true,
false,
[]string{"foo", "bar"},
[]interface{}{1, nil, "foo"},
map[string]string{"foo": "bar"},
map[interface{}]string{"foo": "bar"},
map[interface{}]interface{}{"foo": "bar", "bar": 0},
struct {
Foo string
Bar []interface{}
}{
Foo: "foo",
Bar: []interface{}{nil, nil, nil},
},
&struct {
Foo string
Bar []interface{}
}{
Foo: "foo",
Bar: []interface{}{nil, nil, nil},
},
}
for _, tc := range cases {
// We run the test 100 times to try to tease out variability
// in the runtime in terms of ordering.
valuelist := make([]uint64, 100)
for i := range valuelist {
v, err := Hash(tc, nil)
if err != nil {
t.Fatalf("Error: %s\n\n%#v", err, tc)
}
valuelist[i] = v
}
// Zero is always wrong
if valuelist[0] == 0 {
t.Fatalf("zero hash: %#v", tc)
}
// Make sure all the values match
t.Logf("%#v: %d", tc, valuelist[0])
for i := 1; i < len(valuelist); i++ {
if valuelist[i] != valuelist[0] {
t.Fatalf("non-matching: %d, %d\n\n%#v", i, 0, tc)
}
}
}
}
func TestHash_equal(t *testing.T) {
type testFoo struct{ Name string }
type testBar struct{ Name string }
cases := []struct {
One, Two interface{}
Match bool
}{
{
map[string]string{"foo": "bar"},
map[interface{}]string{"foo": "bar"},
true,
},
{
map[string]interface{}{"1": "1"},
map[string]interface{}{"1": "1", "2": "2"},
false,
},
{
struct{ Fname, Lname string }{"foo", "bar"},
struct{ Fname, Lname string }{"bar", "foo"},
false,
},
{
struct{ Lname, Fname string }{"foo", "bar"},
struct{ Fname, Lname string }{"foo", "bar"},
false,
},
{
struct{ Lname, Fname string }{"foo", "bar"},
struct{ Fname, Lname string }{"bar", "foo"},
true,
},
{
testFoo{"foo"},
testBar{"foo"},
false,
},
{
struct {
Foo string
unexported string
}{
Foo: "bar",
unexported: "baz",
},
struct {
Foo string
unexported string
}{
Foo: "bar",
unexported: "bang",
},
true,
},
}
for _, tc := range cases {
t.Logf("Hashing: %#v", tc.One)
one, err := Hash(tc.One, nil)
t.Logf("Result: %d", one)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
t.Logf("Hashing: %#v", tc.Two)
two, err := Hash(tc.Two, nil)
t.Logf("Result: %d", two)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
func TestHash_equalIgnore(t *testing.T) {
type Test struct {
Name string
UUID string `hash:"ignore"`
}
cases := []struct {
One, Two interface{}
Match bool
}{
{
Test{Name: "foo", UUID: "foo"},
Test{Name: "foo", UUID: "bar"},
true,
},
{
Test{Name: "foo", UUID: "foo"},
Test{Name: "foo", UUID: "foo"},
true,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
func TestHash_equalSet(t *testing.T) {
type Test struct {
Name string
Friends []string `hash:"set"`
}
cases := []struct {
One, Two interface{}
Match bool
}{
{
Test{Name: "foo", Friends: []string{"foo", "bar"}},
Test{Name: "foo", Friends: []string{"bar", "foo"}},
true,
},
{
Test{Name: "foo", Friends: []string{"foo", "bar"}},
Test{Name: "foo", Friends: []string{"foo", "bar"}},
true,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
func TestHash_includable(t *testing.T) {
cases := []struct {
One, Two interface{}
Match bool
}{
{
testIncludable{Value: "foo"},
testIncludable{Value: "foo"},
true,
},
{
testIncludable{Value: "foo", Ignore: "bar"},
testIncludable{Value: "foo"},
true,
},
{
testIncludable{Value: "foo", Ignore: "bar"},
testIncludable{Value: "bar"},
false,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
func TestHash_includableMap(t *testing.T) {
cases := []struct {
One, Two interface{}
Match bool
}{
{
testIncludableMap{Map: map[string]string{"foo": "bar"}},
testIncludableMap{Map: map[string]string{"foo": "bar"}},
true,
},
{
testIncludableMap{Map: map[string]string{"foo": "bar", "ignore": "true"}},
testIncludableMap{Map: map[string]string{"foo": "bar"}},
true,
},
{
testIncludableMap{Map: map[string]string{"foo": "bar", "ignore": "true"}},
testIncludableMap{Map: map[string]string{"bar": "baz"}},
false,
},
}
for _, tc := range cases {
one, err := Hash(tc.One, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.One, err)
}
two, err := Hash(tc.Two, nil)
if err != nil {
t.Fatalf("Failed to hash %#v: %s", tc.Two, err)
}
// Zero is always wrong
if one == 0 {
t.Fatalf("zero hash: %#v", tc.One)
}
// Compare
if (one == two) != tc.Match {
t.Fatalf("bad, expected: %#v\n\n%#v\n\n%#v", tc.Match, tc.One, tc.Two)
}
}
}
type testIncludable struct {
Value string
Ignore string
}
func (t testIncludable) HashInclude(field string, v interface{}) (bool, error) {
return field != "Ignore", nil
}
type testIncludableMap struct {
Map map[string]string
}
func (t testIncludableMap) HashIncludeMap(field string, k, v interface{}) (bool, error) {
if field != "Map" {
return true, nil
}
if s, ok := k.(string); ok && s == "ignore" {
return false, nil
}
return true, nil
}
package hashstructure
// Includable is an interface that can optionally be implemented by
// a struct. It will be called for each field in the struct to check whether
// it should be included in the hash.
type Includable interface {
HashInclude(field string, v interface{}) (bool, error)
}
// IncludableMap is an interface that can optionally be implemented by
// a struct. It will be called when a map-type field is found to ask the
// struct if the map item should be included in the hash.
type IncludableMap interface {
HashIncludeMap(field string, k, v interface{}) (bool, error)
}
...@@ -24,11 +24,11 @@ package cache ...@@ -24,11 +24,11 @@ package cache
// Hashable types must implement a method that returns a key. This key will be // Hashable types must implement a method that returns a key. This key will be
// associated with a cached value. // associated with a cached value.
type Hashable interface { type Hashable interface {
Hash() string Hash() uint64
} }
// HasOnPurge type is (optionally) implemented by cache objects to clean after // HasOnEvict type is (optionally) implemented by cache objects to clean after
// themselves. // themselves.
type HasOnPurge interface { type HasOnEvict interface {
OnPurge() OnEvict()
} }
package exql package exql
import ( import (
"fmt"
"strings" "strings"
) )
type columnT struct { type columnWithAlias struct {
Name string Name string
Alias string Alias string
} }
...@@ -13,8 +12,6 @@ type columnT struct { ...@@ -13,8 +12,6 @@ type columnT struct {
// Column represents a SQL column. // Column represents a SQL column.
type Column struct { type Column struct {
Name interface{} Name interface{}
Alias string
hash hash
} }
var _ = Fragment(&Column{}) var _ = Fragment(&Column{})
...@@ -25,8 +22,8 @@ func ColumnWithName(name string) *Column { ...@@ -25,8 +22,8 @@ func ColumnWithName(name string) *Column {
} }
// Hash returns a unique identifier for the struct. // Hash returns a unique identifier for the struct.
func (c *Column) Hash() string { func (c *Column) Hash() uint64 {
return c.hash.Hash(c) return quickHash(FragmentType_Column, c.Name)
} }
// Compile transforms the ColumnValue into an equivalent SQL representation. // Compile transforms the ColumnValue into an equivalent SQL representation.
...@@ -35,20 +32,17 @@ func (c *Column) Compile(layout *Template) (compiled string, err error) { ...@@ -35,20 +32,17 @@ func (c *Column) Compile(layout *Template) (compiled string, err error) {
return z, nil return z, nil
} }
alias := c.Alias var alias string
switch value := c.Name.(type) { switch value := c.Name.(type) {
case string: case string:
input := trimString(value) value = trimString(value)
chunks := separateByAS(input)
chunks := separateByAS(value)
if len(chunks) == 1 { if len(chunks) == 1 {
chunks = separateBySpace(input) chunks = separateBySpace(value)
} }
name := chunks[0] name := chunks[0]
nameChunks := strings.SplitN(name, layout.ColumnSeparator, 2) nameChunks := strings.SplitN(name, layout.ColumnSeparator, 2)
for i := range nameChunks { for i := range nameChunks {
...@@ -65,17 +59,19 @@ func (c *Column) Compile(layout *Template) (compiled string, err error) { ...@@ -65,17 +59,19 @@ func (c *Column) Compile(layout *Template) (compiled string, err error) {
alias = trimString(chunks[1]) alias = trimString(chunks[1])
alias = layout.MustCompile(layout.IdentifierQuote, Raw{Value: alias}) alias = layout.MustCompile(layout.IdentifierQuote, Raw{Value: alias})
} }
case Raw: case compilable:
compiled = value.String() compiled, err = value.Compile(layout)
if err != nil {
return "", err
}
default: default:
compiled = fmt.Sprintf("%v", c.Name) return "", ErrExpectingHashable
} }
if alias != "" { if alias != "" {
compiled = layout.MustCompile(layout.ColumnAliasLayout, columnT{compiled, alias}) compiled = layout.MustCompile(layout.ColumnAliasLayout, columnWithAlias{compiled, alias})
} }
layout.Write(c, compiled) layout.Write(c, compiled)
return return
} }
...@@ -2,76 +2,36 @@ package exql ...@@ -2,76 +2,36 @@ package exql
import ( import (
"testing" "testing"
)
func TestColumnHash(t *testing.T) {
var s, e string
column := Column{Name: "role.name"}
s = column.Hash() "github.com/stretchr/testify/assert"
e = "*exql.Column:5663680925324531495" )
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
}
func TestColumnString(t *testing.T) { func TestColumnString(t *testing.T) {
column := Column{Name: "role.name"} column := Column{Name: "role.name"}
s, err := column.Compile(defaultTemplate) s, err := column.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `"role"."name"`, s)
}
e := `"role"."name"`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
} }
func TestColumnAs(t *testing.T) { func TestColumnAs(t *testing.T) {
column := Column{Name: "role.name as foo"} column := Column{Name: "role.name as foo"}
s, err := column.Compile(defaultTemplate) s, err := column.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `"role"."name" AS "foo"`, s)
}
e := `"role"."name" AS "foo"`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
} }
func TestColumnImplicitAs(t *testing.T) { func TestColumnImplicitAs(t *testing.T) {
column := Column{Name: "role.name foo"} column := Column{Name: "role.name foo"}
s, err := column.Compile(defaultTemplate) s, err := column.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `"role"."name" AS "foo"`, s)
}
e := `"role"."name" AS "foo"`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
} }
func TestColumnRaw(t *testing.T) { func TestColumnRaw(t *testing.T) {
column := Column{Name: Raw{Value: "role.name As foo"}} column := Column{Name: &Raw{Value: "role.name As foo"}}
s, err := column.Compile(defaultTemplate) s, err := column.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `role.name As foo`, s)
}
e := `role.name As foo`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
} }
func BenchmarkColumnWithName(b *testing.B) { func BenchmarkColumnWithName(b *testing.B) {
...@@ -82,6 +42,7 @@ func BenchmarkColumnWithName(b *testing.B) { ...@@ -82,6 +42,7 @@ func BenchmarkColumnWithName(b *testing.B) {
func BenchmarkColumnHash(b *testing.B) { func BenchmarkColumnHash(b *testing.B) {
c := Column{Name: "name"} c := Column{Name: "name"}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
c.Hash() c.Hash()
} }
...@@ -89,6 +50,7 @@ func BenchmarkColumnHash(b *testing.B) { ...@@ -89,6 +50,7 @@ func BenchmarkColumnHash(b *testing.B) {
func BenchmarkColumnCompile(b *testing.B) { func BenchmarkColumnCompile(b *testing.B) {
c := Column{Name: "name"} c := Column{Name: "name"}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = c.Compile(defaultTemplate) _, _ = c.Compile(defaultTemplate)
} }
...@@ -103,6 +65,7 @@ func BenchmarkColumnCompileNoCache(b *testing.B) { ...@@ -103,6 +65,7 @@ func BenchmarkColumnCompileNoCache(b *testing.B) {
func BenchmarkColumnWithDotCompile(b *testing.B) { func BenchmarkColumnWithDotCompile(b *testing.B) {
c := Column{Name: "role.name"} c := Column{Name: "role.name"}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = c.Compile(defaultTemplate) _, _ = c.Compile(defaultTemplate)
} }
...@@ -110,6 +73,7 @@ func BenchmarkColumnWithDotCompile(b *testing.B) { ...@@ -110,6 +73,7 @@ func BenchmarkColumnWithDotCompile(b *testing.B) {
func BenchmarkColumnWithImplicitAsKeywordCompile(b *testing.B) { func BenchmarkColumnWithImplicitAsKeywordCompile(b *testing.B) {
c := Column{Name: "role.name foo"} c := Column{Name: "role.name foo"}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = c.Compile(defaultTemplate) _, _ = c.Compile(defaultTemplate)
} }
...@@ -117,6 +81,7 @@ func BenchmarkColumnWithImplicitAsKeywordCompile(b *testing.B) { ...@@ -117,6 +81,7 @@ func BenchmarkColumnWithImplicitAsKeywordCompile(b *testing.B) {
func BenchmarkColumnWithAsKeywordCompile(b *testing.B) { func BenchmarkColumnWithAsKeywordCompile(b *testing.B) {
c := Column{Name: "role.name AS foo"} c := Column{Name: "role.name AS foo"}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = c.Compile(defaultTemplate) _, _ = c.Compile(defaultTemplate)
} }
......
...@@ -9,7 +9,6 @@ type ColumnValue struct { ...@@ -9,7 +9,6 @@ type ColumnValue struct {
Column Fragment Column Fragment
Operator string Operator string
Value Fragment Value Fragment
hash hash
} }
var _ = Fragment(&ColumnValue{}) var _ = Fragment(&ColumnValue{})
...@@ -21,8 +20,8 @@ type columnValueT struct { ...@@ -21,8 +20,8 @@ type columnValueT struct {
} }
// Hash returns a unique identifier for the struct. // Hash returns a unique identifier for the struct.
func (c *ColumnValue) Hash() string { func (c *ColumnValue) Hash() uint64 {
return c.hash.Hash(c) return quickHash(FragmentType_ColumnValue, c.Column, c.Operator, c.Value)
} }
// Compile transforms the ColumnValue into an equivalent SQL representation. // Compile transforms the ColumnValue into an equivalent SQL representation.
...@@ -58,7 +57,6 @@ func (c *ColumnValue) Compile(layout *Template) (compiled string, err error) { ...@@ -58,7 +57,6 @@ func (c *ColumnValue) Compile(layout *Template) (compiled string, err error) {
// ColumnValues represents an array of ColumnValue // ColumnValues represents an array of ColumnValue
type ColumnValues struct { type ColumnValues struct {
ColumnValues []Fragment ColumnValues []Fragment
hash hash
} }
var _ = Fragment(&ColumnValues{}) var _ = Fragment(&ColumnValues{})
...@@ -71,13 +69,16 @@ func JoinColumnValues(values ...Fragment) *ColumnValues { ...@@ -71,13 +69,16 @@ func JoinColumnValues(values ...Fragment) *ColumnValues {
// Insert adds a column to the columns array. // Insert adds a column to the columns array.
func (c *ColumnValues) Insert(values ...Fragment) *ColumnValues { func (c *ColumnValues) Insert(values ...Fragment) *ColumnValues {
c.ColumnValues = append(c.ColumnValues, values...) c.ColumnValues = append(c.ColumnValues, values...)
c.hash.Reset()
return c return c
} }
// Hash returns a unique identifier for the struct. // Hash returns a unique identifier for the struct.
func (c *ColumnValues) Hash() string { func (c *ColumnValues) Hash() uint64 {
return c.hash.Hash(c) h := initHash(FragmentType_ColumnValues)
for i := range c.ColumnValues {
h = addToHash(h, c.ColumnValues[i])
}
return h
} }
// Compile transforms the ColumnValues into its SQL representation. // Compile transforms the ColumnValues into its SQL representation.
......
...@@ -2,61 +2,20 @@ package exql ...@@ -2,61 +2,20 @@ package exql
import ( import (
"testing" "testing"
)
func TestColumnValueHash(t *testing.T) {
var s, e string
c := &ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(1)}
s = c.Hash()
e = `*exql.ColumnValue:4950005282640920683`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
}
func TestColumnValuesHash(t *testing.T) {
var s, e string
c := JoinColumnValues( "github.com/stretchr/testify/assert"
&ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(1)},
&ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(2)},
) )
s = c.Hash()
e = `*exql.ColumnValues:8728513848368010747`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
}
func TestColumnValue(t *testing.T) { func TestColumnValue(t *testing.T) {
cv := &ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(1)} cv := &ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(1)}
s, err := cv.Compile(defaultTemplate) s, err := cv.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `"id" = '1'`, s)
}
e := `"id" = '1'`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
cv = &ColumnValue{Column: ColumnWithName("date"), Operator: "=", Value: NewValue(RawValue("NOW()"))}
cv = &ColumnValue{Column: ColumnWithName("date"), Operator: "=", Value: &Raw{Value: "NOW()"}}
s, err = cv.Compile(defaultTemplate) s, err = cv.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `"date" = NOW()`, s)
}
e = `"date" = NOW()`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
} }
func TestColumnValues(t *testing.T) { func TestColumnValues(t *testing.T) {
...@@ -69,14 +28,8 @@ func TestColumnValues(t *testing.T) { ...@@ -69,14 +28,8 @@ func TestColumnValues(t *testing.T) {
) )
s, err := cvs.Compile(defaultTemplate) s, err := cvs.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `"id" > '8', "other"."id" < 100, "name" = 'Haruki Murakami', "created" >= NOW(), "modified" <= NOW()`, s)
}
e := `"id" > '8', "other"."id" < 100, "name" = 'Haruki Murakami', "created" >= NOW(), "modified" <= NOW()`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
} }
func BenchmarkNewColumnValue(b *testing.B) { func BenchmarkNewColumnValue(b *testing.B) {
...@@ -87,6 +40,7 @@ func BenchmarkNewColumnValue(b *testing.B) { ...@@ -87,6 +40,7 @@ func BenchmarkNewColumnValue(b *testing.B) {
func BenchmarkColumnValueHash(b *testing.B) { func BenchmarkColumnValueHash(b *testing.B) {
cv := &ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(1)} cv := &ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(1)}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
cv.Hash() cv.Hash()
} }
...@@ -94,6 +48,7 @@ func BenchmarkColumnValueHash(b *testing.B) { ...@@ -94,6 +48,7 @@ func BenchmarkColumnValueHash(b *testing.B) {
func BenchmarkColumnValueCompile(b *testing.B) { func BenchmarkColumnValueCompile(b *testing.B) {
cv := &ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(1)} cv := &ColumnValue{Column: ColumnWithName("id"), Operator: "=", Value: NewValue(1)}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = cv.Compile(defaultTemplate) _, _ = cv.Compile(defaultTemplate)
} }
...@@ -126,6 +81,7 @@ func BenchmarkColumnValuesHash(b *testing.B) { ...@@ -126,6 +81,7 @@ func BenchmarkColumnValuesHash(b *testing.B) {
&ColumnValue{Column: ColumnWithName("created"), Operator: ">=", Value: NewValue(Raw{Value: "NOW()"})}, &ColumnValue{Column: ColumnWithName("created"), Operator: ">=", Value: NewValue(Raw{Value: "NOW()"})},
&ColumnValue{Column: ColumnWithName("modified"), Operator: "<=", Value: NewValue(Raw{Value: "NOW()"})}, &ColumnValue{Column: ColumnWithName("modified"), Operator: "<=", Value: NewValue(Raw{Value: "NOW()"})},
) )
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
cvs.Hash() cvs.Hash()
} }
...@@ -139,6 +95,7 @@ func BenchmarkColumnValuesCompile(b *testing.B) { ...@@ -139,6 +95,7 @@ func BenchmarkColumnValuesCompile(b *testing.B) {
&ColumnValue{Column: ColumnWithName("created"), Operator: ">=", Value: NewValue(Raw{Value: "NOW()"})}, &ColumnValue{Column: ColumnWithName("created"), Operator: ">=", Value: NewValue(Raw{Value: "NOW()"})},
&ColumnValue{Column: ColumnWithName("modified"), Operator: "<=", Value: NewValue(Raw{Value: "NOW()"})}, &ColumnValue{Column: ColumnWithName("modified"), Operator: "<=", Value: NewValue(Raw{Value: "NOW()"})},
) )
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = cvs.Compile(defaultTemplate) _, _ = cvs.Compile(defaultTemplate)
} }
......
...@@ -7,14 +7,17 @@ import ( ...@@ -7,14 +7,17 @@ import (
// Columns represents an array of Column. // Columns represents an array of Column.
type Columns struct { type Columns struct {
Columns []Fragment Columns []Fragment
hash hash
} }
var _ = Fragment(&Columns{}) var _ = Fragment(&Columns{})
// Hash returns a unique identifier. // Hash returns a unique identifier.
func (c *Columns) Hash() string { func (c *Columns) Hash() uint64 {
return c.hash.Hash(c) h := initHash(FragmentType_Columns)
for i := range c.Columns {
h = addToHash(h, c.Columns[i])
}
return h
} }
// JoinColumns creates and returns an array of Column. // JoinColumns creates and returns an array of Column.
......
...@@ -2,6 +2,8 @@ package exql ...@@ -2,6 +2,8 @@ package exql
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestColumns(t *testing.T) { func TestColumns(t *testing.T) {
...@@ -14,14 +16,8 @@ func TestColumns(t *testing.T) { ...@@ -14,14 +16,8 @@ func TestColumns(t *testing.T) {
) )
s, err := columns.Compile(defaultTemplate) s, err := columns.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `"id", "customer", "service_id", "role"."name", "role"."id"`, s)
}
e := `"id", "customer", "service_id", "role"."name", "role"."id"`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
} }
func BenchmarkJoinColumns(b *testing.B) { func BenchmarkJoinColumns(b *testing.B) {
...@@ -42,6 +38,7 @@ func BenchmarkColumnsHash(b *testing.B) { ...@@ -42,6 +38,7 @@ func BenchmarkColumnsHash(b *testing.B) {
&Column{Name: "role.name"}, &Column{Name: "role.name"},
&Column{Name: "role.id"}, &Column{Name: "role.id"},
) )
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
c.Hash() c.Hash()
} }
...@@ -55,6 +52,7 @@ func BenchmarkColumnsCompile(b *testing.B) { ...@@ -55,6 +52,7 @@ func BenchmarkColumnsCompile(b *testing.B) {
&Column{Name: "role.name"}, &Column{Name: "role.name"},
&Column{Name: "role.id"}, &Column{Name: "role.id"},
) )
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = c.Compile(defaultTemplate) _, _ = c.Compile(defaultTemplate)
} }
......
...@@ -3,7 +3,6 @@ package exql ...@@ -3,7 +3,6 @@ package exql
// Database represents a SQL database. // Database represents a SQL database.
type Database struct { type Database struct {
Name string Name string
hash hash
} }
var _ = Fragment(&Database{}) var _ = Fragment(&Database{})
...@@ -14,8 +13,8 @@ func DatabaseWithName(name string) *Database { ...@@ -14,8 +13,8 @@ func DatabaseWithName(name string) *Database {
} }
// Hash returns a unique identifier for the struct. // Hash returns a unique identifier for the struct.
func (d *Database) Hash() string { func (d *Database) Hash() uint64 {
return d.hash.Hash(d) return quickHash(FragmentType_Database, d.Name)
} }
// Compile transforms the Database into an equivalent SQL representation. // Compile transforms the Database into an equivalent SQL representation.
......
package exql package exql
import ( import (
"fmt" "strconv"
"testing" "testing"
)
func TestDatabaseHash(t *testing.T) {
var s, e string
column := Database{Name: "users"}
s = column.Hash() "github.com/stretchr/testify/assert"
e = `*exql.Database:16777957551305673389` )
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
}
func TestDatabaseCompile(t *testing.T) { func TestDatabaseCompile(t *testing.T) {
column := Database{Name: "name"} column := Database{Name: "name"}
s, err := column.Compile(defaultTemplate) s, err := column.Compile(defaultTemplate)
if err != nil { assert.NoError(t, err)
t.Fatal() assert.Equal(t, `"name"`, s)
}
e := `"name"`
if s != e {
t.Fatalf("Got: %s, Expecting: %s", s, e)
}
} }
func BenchmarkDatabaseHash(b *testing.B) { func BenchmarkDatabaseHash(b *testing.B) {
c := Database{Name: "name"} c := Database{Name: "name"}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
c.Hash() c.Hash()
} }
...@@ -41,6 +24,7 @@ func BenchmarkDatabaseHash(b *testing.B) { ...@@ -41,6 +24,7 @@ func BenchmarkDatabaseHash(b *testing.B) {
func BenchmarkDatabaseCompile(b *testing.B) { func BenchmarkDatabaseCompile(b *testing.B) {
c := Database{Name: "name"} c := Database{Name: "name"}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = c.Compile(defaultTemplate) _, _ = c.Compile(defaultTemplate)
} }
...@@ -55,7 +39,7 @@ func BenchmarkDatabaseCompileNoCache(b *testing.B) { ...@@ -55,7 +39,7 @@ func BenchmarkDatabaseCompileNoCache(b *testing.B) {
func BenchmarkDatabaseCompileNoCache2(b *testing.B) { func BenchmarkDatabaseCompileNoCache2(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
c := Database{Name: fmt.Sprintf("name: %v", i)} c := Database{Name: strconv.Itoa(i)}
_, _ = c.Compile(defaultTemplate) _, _ = c.Compile(defaultTemplate)
} }
} }
package exql
import (
"errors"
)
var ErrExpectingHashable = errors.New("expecting hashable value")
...@@ -3,7 +3,6 @@ package exql ...@@ -3,7 +3,6 @@ package exql
// GroupBy represents a SQL's "group by" statement. // GroupBy represents a SQL's "group by" statement.
type GroupBy struct { type GroupBy struct {
Columns Fragment Columns Fragment
hash hash
} }
var _ = Fragment(&GroupBy{}) var _ = Fragment(&GroupBy{})
...@@ -13,8 +12,8 @@ type groupByT struct { ...@@ -13,8 +12,8 @@ type groupByT struct {
} }
// Hash returns a unique identifier. // Hash returns a unique identifier.
func (g *GroupBy) Hash() string { func (g *GroupBy) Hash() uint64 {
return g.hash.Hash(g) return quickHash(FragmentType_GroupBy, g.Columns)
} }
// GroupByColumns creates and returns a GroupBy with the given column. // GroupByColumns creates and returns a GroupBy with the given column.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment