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
|
||||
- Index fields inside [JSON](#json-indexes) documents
|
||||
- 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
|
||||
- Flexible [iteration](#iterating) of data; ascending, descending, and ranges
|
||||
- [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}
|
||||
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
|
||||
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.
|
||||
EverySecond = 1
|
||||
// Always is used to sync data after every write to disk.
|
||||
// Very very slow. Very safe.
|
||||
// Slow. Very safe.
|
||||
Always = 2
|
||||
)
|
||||
|
||||
|
@ -202,7 +202,7 @@ type index struct {
|
|||
// There are some default less function that can be used such as
|
||||
// IndexString, IndexBinary, etc.
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -229,7 +229,7 @@ func (db *DB) CreateSpatialIndex(name, pattern string,
|
|||
func (db *DB) createIndex(
|
||||
name string,
|
||||
pattern string,
|
||||
less func(a, b string) bool,
|
||||
lessers []func(a, b string) bool,
|
||||
rect func(item string) (min, max []float64),
|
||||
) error {
|
||||
db.mu.Lock()
|
||||
|
@ -243,6 +243,25 @@ func (db *DB) createIndex(
|
|||
if _, ok := db.idxs[name]; ok {
|
||||
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{
|
||||
name: name,
|
||||
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.")
|
||||
}
|
||||
}
|
||||
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) {
|
||||
item := &dbItem{key: "key", val: "val"}
|
||||
if !item.expiresAt().Equal(maxTime) {
|
||||
|
|
Loading…
Reference in New Issue