multi value indexes

This commit is contained in:
Josh Baker 2016-08-18 09:08:30 -07:00
parent 491b8f847f
commit 1daaa16172
3 changed files with 190 additions and 3 deletions

View File

@ -33,6 +33,7 @@ Features
- [Spatial indexing](#spatial-indexes) for up to 20 dimensions; Useful for Geospatial data - [Spatial indexing](#spatial-indexes) for up to 20 dimensions; Useful for Geospatial data
- Index fields inside [JSON](#json-indexes) documents - Index fields inside [JSON](#json-indexes) documents
- Create [custom indexes](#custom-indexes) for any data type - Create [custom indexes](#custom-indexes) for any data type
- Support for [multi value indexes](#multi-value-index); Similar to a SQL multi column index
- [Built-in types](#built-in-types) that are easy to get up & running; String, Uint, Int, Float - [Built-in types](#built-in-types) that are easy to get up & running; String, Uint, Int, Float
- Flexible [iteration](#iterating) of data; ascending, descending, and ranges - Flexible [iteration](#iterating) of data; ascending, descending, and ranges
- [Durable append-only file](#append-only-file) format for persistence. - [Durable append-only file](#append-only-file) format for persistence.
@ -411,6 +412,41 @@ Order by age range 30-50
1: {"name":{"first":"Tom","last":"Johnson"},"age":38} 1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
2: {"name":{"first":"Janet","last":"Prichard"},"age":47} 2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
``` ```
## Multi Value Index
With BuntDB it's possible to join multiple values on a single index.
This is similar to a [multi column index](http://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) in a traditional SQL database.
In this example we are creating a multi value index on "name.last" and "age":
```go
db, _ := buntdb.Open(":memory:")
db.CreateIndex("last_name_age", "*", buntdb.IndexJSON("name.last"), buntdb.IndexJSON("age"))
db.Update(func(tx *buntdb.Tx) error {
tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil)
tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil)
tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil)
tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil)
tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil)
tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil)
return nil
})
db.View(func(tx *buntdb.Tx) error {
tx.Ascend("last_name_age", func(key, value string) bool {
fmt.Printf("%s: %s\n", key, value)
return true
})
return nil
})
// Output:
// 5: {"name":{"first":"Sam","last":"Anderson"},"age":51}
// 3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
// 4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
// 1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
// 6: {"name":{"first":"Melinda","last":"Prichard"},"age":44}
// 2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
```
## Data Expiration ## Data Expiration
Items can be automatically evicted by using the `SetOptions` object in the `Set` function to set a `TTL`. Items can be automatically evicted by using the `SetOptions` object in the `Set` function to set a `TTL`.

View File

@ -89,7 +89,7 @@ const (
// This is the recommended setting. // This is the recommended setting.
EverySecond = 1 EverySecond = 1
// Always is used to sync data after every write to disk. // Always is used to sync data after every write to disk.
// Very very slow. Very safe. // Slow. Very safe.
Always = 2 Always = 2
) )
@ -202,7 +202,7 @@ type index struct {
// There are some default less function that can be used such as // There are some default less function that can be used such as
// IndexString, IndexBinary, etc. // IndexString, IndexBinary, etc.
func (db *DB) CreateIndex(name, pattern string, func (db *DB) CreateIndex(name, pattern string,
less func(a, b string) bool) error { less ...func(a, b string) bool) error {
return db.createIndex(name, pattern, less, nil) return db.createIndex(name, pattern, less, nil)
} }
@ -229,7 +229,7 @@ func (db *DB) CreateSpatialIndex(name, pattern string,
func (db *DB) createIndex( func (db *DB) createIndex(
name string, name string,
pattern string, pattern string,
less func(a, b string) bool, lessers []func(a, b string) bool,
rect func(item string) (min, max []float64), rect func(item string) (min, max []float64),
) error { ) error {
db.mu.Lock() db.mu.Lock()
@ -243,6 +243,25 @@ func (db *DB) createIndex(
if _, ok := db.idxs[name]; ok { if _, ok := db.idxs[name]; ok {
return ErrIndexExists return ErrIndexExists
} }
var less func(a, b string) bool
switch len(lessers) {
default:
less = func(a, b string) bool {
for i := 0; i < len(lessers)-1; i++ {
if lessers[i](a, b) {
return true
}
if lessers[i](b, a) {
return false
}
}
return lessers[len(lessers)-1](a, b)
}
case 0:
less = func(a, b string) bool { return false }
case 1:
less = lessers[0]
}
idx := &index{ idx := &index{
name: name, name: name,
pattern: pattern, pattern: pattern,

View File

@ -414,6 +414,138 @@ func TestVariousTx(t *testing.T) {
t.Fatalf("should not be able to perform transactionso on a closed database.") t.Fatalf("should not be able to perform transactionso on a closed database.")
} }
} }
func ExampleDB_CreateIndex_jSON() {
db, _ := Open(":memory:")
db.CreateIndex("last_name", "*", IndexJSON("name.last"))
db.CreateIndex("age", "*", IndexJSON("age"))
db.Update(func(tx *Tx) error {
tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil)
tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil)
tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil)
tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil)
return nil
})
db.View(func(tx *Tx) error {
fmt.Println("Order by last name")
tx.Ascend("last_name", func(key, value string) bool {
fmt.Printf("%s: %s\n", key, value)
return true
})
fmt.Println("Order by age")
tx.Ascend("age", func(key, value string) bool {
fmt.Printf("%s: %s\n", key, value)
return true
})
fmt.Println("Order by age range 30-50")
tx.AscendRange("age", `{"age":30}`, `{"age":50}`, func(key, value string) bool {
fmt.Printf("%s: %s\n", key, value)
return true
})
return nil
})
// Output:
// Order by last name
// 3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
// 4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
// 1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
// 2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
// Order by age
// 4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
// 1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
// 2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
// 3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
// Order by age range 30-50
// 1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
// 2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
}
func ExampleDB_CreateIndex_strings() {
db, _ := Open(":memory:")
db.CreateIndex("name", "*", IndexString)
db.Update(func(tx *Tx) error {
tx.Set("1", "Tom", nil)
tx.Set("2", "Janet", nil)
tx.Set("3", "Carol", nil)
tx.Set("4", "Alan", nil)
tx.Set("5", "Sam", nil)
tx.Set("6", "Melinda", nil)
return nil
})
db.View(func(tx *Tx) error {
tx.Ascend("name", func(key, value string) bool {
fmt.Printf("%s: %s\n", key, value)
return true
})
return nil
})
// Output:
//4: Alan
//3: Carol
//2: Janet
//6: Melinda
//5: Sam
//1: Tom
}
func ExampleDB_CreateIndex_ints() {
db, _ := Open(":memory:")
db.CreateIndex("age", "*", IndexInt)
db.Update(func(tx *Tx) error {
tx.Set("1", "30", nil)
tx.Set("2", "51", nil)
tx.Set("3", "16", nil)
tx.Set("4", "76", nil)
tx.Set("5", "23", nil)
tx.Set("6", "43", nil)
return nil
})
db.View(func(tx *Tx) error {
tx.Ascend("age", func(key, value string) bool {
fmt.Printf("%s: %s\n", key, value)
return true
})
return nil
})
// Output:
//3: 16
//5: 23
//1: 30
//6: 43
//2: 51
//4: 76
}
func ExampleDB_CreateIndex_multipleFields() {
db, _ := Open(":memory:")
db.CreateIndex("last_name_age", "*", IndexJSON("name.last"), IndexJSON("age"))
db.Update(func(tx *Tx) error {
tx.Set("1", `{"name":{"first":"Tom","last":"Johnson"},"age":38}`, nil)
tx.Set("2", `{"name":{"first":"Janet","last":"Prichard"},"age":47}`, nil)
tx.Set("3", `{"name":{"first":"Carol","last":"Anderson"},"age":52}`, nil)
tx.Set("4", `{"name":{"first":"Alan","last":"Cooper"},"age":28}`, nil)
tx.Set("5", `{"name":{"first":"Sam","last":"Anderson"},"age":51}`, nil)
tx.Set("6", `{"name":{"first":"Melinda","last":"Prichard"},"age":44}`, nil)
return nil
})
db.View(func(tx *Tx) error {
tx.Ascend("last_name_age", func(key, value string) bool {
fmt.Printf("%s: %s\n", key, value)
return true
})
return nil
})
// Output:
//5: {"name":{"first":"Sam","last":"Anderson"},"age":51}
//3: {"name":{"first":"Carol","last":"Anderson"},"age":52}
//4: {"name":{"first":"Alan","last":"Cooper"},"age":28}
//1: {"name":{"first":"Tom","last":"Johnson"},"age":38}
//6: {"name":{"first":"Melinda","last":"Prichard"},"age":44}
//2: {"name":{"first":"Janet","last":"Prichard"},"age":47}
}
func TestNoExpiringItem(t *testing.T) { func TestNoExpiringItem(t *testing.T) {
item := &dbItem{key: "key", val: "val"} item := &dbItem{key: "key", val: "val"}
if !item.expiresAt().Equal(maxTime) { if !item.expiresAt().Equal(maxTime) {