mirror of https://github.com/tidwall/buntdb.git
multi value indexes
This commit is contained in:
parent
491b8f847f
commit
1daaa16172
36
README.md
36
README.md
|
@ -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`.
|
||||||
|
|
25
buntdb.go
25
buntdb.go
|
@ -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,
|
||||||
|
|
132
buntdb_test.go
132
buntdb_test.go
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue