clone
This commit is contained in:
21
vendor/github.com/asdine/storm/LICENSE
generated
vendored
Normal file
21
vendor/github.com/asdine/storm/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) [2017] [Asdine El Hrychy]
|
||||
|
||||
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.
|
35
vendor/github.com/asdine/storm/codec/gob/gob.go
generated
vendored
Normal file
35
vendor/github.com/asdine/storm/codec/gob/gob.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Package gob contains a codec to encode and decode entities in Gob format
|
||||
package gob
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
)
|
||||
|
||||
const name = "gob"
|
||||
|
||||
// Codec serializing objects using the gob package.
|
||||
// See https://golang.org/pkg/encoding/gob/
|
||||
var Codec = new(gobCodec)
|
||||
|
||||
type gobCodec int
|
||||
|
||||
func (c gobCodec) Marshal(v interface{}) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
enc := gob.NewEncoder(&b)
|
||||
err := enc.Encode(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c gobCodec) Unmarshal(b []byte, v interface{}) error {
|
||||
r := bytes.NewReader(b)
|
||||
dec := gob.NewDecoder(r)
|
||||
return dec.Decode(v)
|
||||
}
|
||||
|
||||
func (c gobCodec) Name() string {
|
||||
return name
|
||||
}
|
32
vendor/github.com/asdine/storm/v3/.gitignore
generated
vendored
Normal file
32
vendor/github.com/asdine/storm/v3/.gitignore
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.iml
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# Golang vendor folder
|
||||
/vendor/
|
19
vendor/github.com/asdine/storm/v3/.travis.yml
generated
vendored
Normal file
19
vendor/github.com/asdine/storm/v3/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
language: go
|
||||
|
||||
before_install:
|
||||
- go get github.com/stretchr/testify
|
||||
|
||||
env: GO111MODULE=on
|
||||
|
||||
go:
|
||||
- "1.13.x"
|
||||
- "1.14.x"
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go mod vendor
|
||||
- go test -mod vendor -race -v ./...
|
21
vendor/github.com/asdine/storm/v3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/asdine/storm/v3/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) [2017] [Asdine El Hrychy]
|
||||
|
||||
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.
|
643
vendor/github.com/asdine/storm/v3/README.md
generated
vendored
Normal file
643
vendor/github.com/asdine/storm/v3/README.md
generated
vendored
Normal file
@ -0,0 +1,643 @@
|
||||
# Storm
|
||||
|
||||
[](https://travis-ci.org/asdine/storm)
|
||||
[](https://godoc.org/github.com/asdine/storm)
|
||||
|
||||
Storm is a simple and powerful toolkit for [BoltDB](https://github.com/coreos/bbolt). Basically, Storm provides indexes, a wide range of methods to store and fetch data, an advanced query system, and much more.
|
||||
|
||||
In addition to the examples below, see also the [examples in the GoDoc](https://godoc.org/github.com/asdine/storm#pkg-examples).
|
||||
|
||||
_For extended queries and support for [Badger](https://github.com/dgraph-io/badger), see also [Genji](https://github.com/asdine/genji)_
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Getting Started](#getting-started)
|
||||
- [Import Storm](#import-storm)
|
||||
- [Open a database](#open-a-database)
|
||||
- [Simple CRUD system](#simple-crud-system)
|
||||
- [Declare your structures](#declare-your-structures)
|
||||
- [Save your object](#save-your-object)
|
||||
- [Auto Increment](#auto-increment)
|
||||
- [Simple queries](#simple-queries)
|
||||
- [Fetch one object](#fetch-one-object)
|
||||
- [Fetch multiple objects](#fetch-multiple-objects)
|
||||
- [Fetch all objects](#fetch-all-objects)
|
||||
- [Fetch all objects sorted by index](#fetch-all-objects-sorted-by-index)
|
||||
- [Fetch a range of objects](#fetch-a-range-of-objects)
|
||||
- [Fetch objects by prefix](#fetch-objects-by-prefix)
|
||||
- [Skip, Limit and Reverse](#skip-limit-and-reverse)
|
||||
- [Delete an object](#delete-an-object)
|
||||
- [Update an object](#update-an-object)
|
||||
- [Initialize buckets and indexes before saving an object](#initialize-buckets-and-indexes-before-saving-an-object)
|
||||
- [Drop a bucket](#drop-a-bucket)
|
||||
- [Re-index a bucket](#re-index-a-bucket)
|
||||
- [Advanced queries](#advanced-queries)
|
||||
- [Transactions](#transactions)
|
||||
- [Options](#options)
|
||||
- [BoltOptions](#boltoptions)
|
||||
- [MarshalUnmarshaler](#marshalunmarshaler)
|
||||
- [Provided Codecs](#provided-codecs)
|
||||
- [Use existing Bolt connection](#use-existing-bolt-connection)
|
||||
- [Batch mode](#batch-mode)
|
||||
- [Nodes and nested buckets](#nodes-and-nested-buckets)
|
||||
- [Node options](#node-options)
|
||||
- [Simple Key/Value store](#simple-keyvalue-store)
|
||||
- [BoltDB](#boltdb)
|
||||
- [License](#license)
|
||||
- [Credits](#credits)
|
||||
|
||||
## Getting Started
|
||||
|
||||
```bash
|
||||
GO111MODULE=on go get -u github.com/asdine/storm/v3
|
||||
```
|
||||
|
||||
## Import Storm
|
||||
|
||||
```go
|
||||
import "github.com/asdine/storm/v3"
|
||||
```
|
||||
|
||||
## Open a database
|
||||
|
||||
Quick way of opening a database
|
||||
|
||||
```go
|
||||
db, err := storm.Open("my.db")
|
||||
|
||||
defer db.Close()
|
||||
```
|
||||
|
||||
`Open` can receive multiple options to customize the way it behaves. See [Options](#options) below
|
||||
|
||||
## Simple CRUD system
|
||||
|
||||
### Declare your structures
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ID int // primary key
|
||||
Group string `storm:"index"` // this field will be indexed
|
||||
Email string `storm:"unique"` // this field will be indexed with a unique constraint
|
||||
Name string // this field will not be indexed
|
||||
Age int `storm:"index"`
|
||||
}
|
||||
```
|
||||
|
||||
The primary key can be of any type as long as it is not a zero value. Storm will search for the tag `id`, if not present Storm will search for a field named `ID`.
|
||||
|
||||
```go
|
||||
type User struct {
|
||||
ThePrimaryKey string `storm:"id"`// primary key
|
||||
Group string `storm:"index"` // this field will be indexed
|
||||
Email string `storm:"unique"` // this field will be indexed with a unique constraint
|
||||
Name string // this field will not be indexed
|
||||
}
|
||||
```
|
||||
|
||||
Storm handles tags in nested structures with the `inline` tag
|
||||
|
||||
```go
|
||||
type Base struct {
|
||||
Ident bson.ObjectId `storm:"id"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Base `storm:"inline"`
|
||||
Group string `storm:"index"`
|
||||
Email string `storm:"unique"`
|
||||
Name string
|
||||
CreatedAt time.Time `storm:"index"`
|
||||
}
|
||||
```
|
||||
|
||||
### Save your object
|
||||
|
||||
```go
|
||||
user := User{
|
||||
ID: 10,
|
||||
Group: "staff",
|
||||
Email: "john@provider.com",
|
||||
Name: "John",
|
||||
Age: 21,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := db.Save(&user)
|
||||
// err == nil
|
||||
|
||||
user.ID++
|
||||
err = db.Save(&user)
|
||||
// err == storm.ErrAlreadyExists
|
||||
```
|
||||
|
||||
That's it.
|
||||
|
||||
`Save` creates or updates all the required indexes and buckets, checks the unique constraints and saves the object to the store.
|
||||
|
||||
#### Auto Increment
|
||||
|
||||
Storm can auto increment integer values so you don't have to worry about that when saving your objects. Also, the new value is automatically inserted in your field.
|
||||
|
||||
```go
|
||||
|
||||
type Product struct {
|
||||
Pk int `storm:"id,increment"` // primary key with auto increment
|
||||
Name string
|
||||
IntegerField uint64 `storm:"increment"`
|
||||
IndexedIntegerField uint32 `storm:"index,increment"`
|
||||
UniqueIntegerField int16 `storm:"unique,increment=100"` // the starting value can be set
|
||||
}
|
||||
|
||||
p := Product{Name: "Vaccum Cleaner"}
|
||||
|
||||
fmt.Println(p.Pk)
|
||||
fmt.Println(p.IntegerField)
|
||||
fmt.Println(p.IndexedIntegerField)
|
||||
fmt.Println(p.UniqueIntegerField)
|
||||
// 0
|
||||
// 0
|
||||
// 0
|
||||
// 0
|
||||
|
||||
_ = db.Save(&p)
|
||||
|
||||
fmt.Println(p.Pk)
|
||||
fmt.Println(p.IntegerField)
|
||||
fmt.Println(p.IndexedIntegerField)
|
||||
fmt.Println(p.UniqueIntegerField)
|
||||
// 1
|
||||
// 1
|
||||
// 1
|
||||
// 100
|
||||
|
||||
```
|
||||
|
||||
### Simple queries
|
||||
|
||||
Any object can be fetched, indexed or not. Storm uses indexes when available, otherwise it uses the [query system](#advanced-queries).
|
||||
|
||||
#### Fetch one object
|
||||
|
||||
```go
|
||||
var user User
|
||||
err := db.One("Email", "john@provider.com", &user)
|
||||
// err == nil
|
||||
|
||||
err = db.One("Name", "John", &user)
|
||||
// err == nil
|
||||
|
||||
err = db.One("Name", "Jack", &user)
|
||||
// err == storm.ErrNotFound
|
||||
```
|
||||
|
||||
#### Fetch multiple objects
|
||||
|
||||
```go
|
||||
var users []User
|
||||
err := db.Find("Group", "staff", &users)
|
||||
```
|
||||
|
||||
#### Fetch all objects
|
||||
|
||||
```go
|
||||
var users []User
|
||||
err := db.All(&users)
|
||||
```
|
||||
|
||||
#### Fetch all objects sorted by index
|
||||
|
||||
```go
|
||||
var users []User
|
||||
err := db.AllByIndex("CreatedAt", &users)
|
||||
```
|
||||
|
||||
#### Fetch a range of objects
|
||||
|
||||
```go
|
||||
var users []User
|
||||
err := db.Range("Age", 10, 21, &users)
|
||||
```
|
||||
|
||||
#### Fetch objects by prefix
|
||||
|
||||
```go
|
||||
var users []User
|
||||
err := db.Prefix("Name", "Jo", &users)
|
||||
```
|
||||
|
||||
#### Skip, Limit and Reverse
|
||||
|
||||
```go
|
||||
var users []User
|
||||
err := db.Find("Group", "staff", &users, storm.Skip(10))
|
||||
err = db.Find("Group", "staff", &users, storm.Limit(10))
|
||||
err = db.Find("Group", "staff", &users, storm.Reverse())
|
||||
err = db.Find("Group", "staff", &users, storm.Limit(10), storm.Skip(10), storm.Reverse())
|
||||
|
||||
err = db.All(&users, storm.Limit(10), storm.Skip(10), storm.Reverse())
|
||||
err = db.AllByIndex("CreatedAt", &users, storm.Limit(10), storm.Skip(10), storm.Reverse())
|
||||
err = db.Range("Age", 10, 21, &users, storm.Limit(10), storm.Skip(10), storm.Reverse())
|
||||
```
|
||||
|
||||
#### Delete an object
|
||||
|
||||
```go
|
||||
err := db.DeleteStruct(&user)
|
||||
```
|
||||
|
||||
#### Update an object
|
||||
|
||||
```go
|
||||
// Update multiple fields
|
||||
err := db.Update(&User{ID: 10, Name: "Jack", Age: 45})
|
||||
|
||||
// Update a single field
|
||||
err := db.UpdateField(&User{ID: 10}, "Age", 0)
|
||||
```
|
||||
|
||||
#### Initialize buckets and indexes before saving an object
|
||||
|
||||
```go
|
||||
err := db.Init(&User{})
|
||||
```
|
||||
|
||||
Useful when starting your application
|
||||
|
||||
#### Drop a bucket
|
||||
|
||||
Using the struct
|
||||
|
||||
```go
|
||||
err := db.Drop(&User)
|
||||
```
|
||||
|
||||
Using the bucket name
|
||||
|
||||
```go
|
||||
err := db.Drop("User")
|
||||
```
|
||||
|
||||
#### Re-index a bucket
|
||||
|
||||
```go
|
||||
err := db.ReIndex(&User{})
|
||||
```
|
||||
|
||||
Useful when the structure has changed
|
||||
|
||||
### Advanced queries
|
||||
|
||||
For more complex queries, you can use the `Select` method.
|
||||
`Select` takes any number of [`Matcher`](https://godoc.org/github.com/asdine/storm/q#Matcher) from the [`q`](https://godoc.org/github.com/asdine/storm/q) package.
|
||||
|
||||
Here are some common Matchers:
|
||||
|
||||
```go
|
||||
// Equality
|
||||
q.Eq("Name", John)
|
||||
|
||||
// Strictly greater than
|
||||
q.Gt("Age", 7)
|
||||
|
||||
// Lesser than or equal to
|
||||
q.Lte("Age", 77)
|
||||
|
||||
// Regex with name that starts with the letter D
|
||||
q.Re("Name", "^D")
|
||||
|
||||
// In the given slice of values
|
||||
q.In("Group", []string{"Staff", "Admin"})
|
||||
|
||||
// Comparing fields
|
||||
q.EqF("FieldName", "SecondFieldName")
|
||||
q.LtF("FieldName", "SecondFieldName")
|
||||
q.GtF("FieldName", "SecondFieldName")
|
||||
q.LteF("FieldName", "SecondFieldName")
|
||||
q.GteF("FieldName", "SecondFieldName")
|
||||
```
|
||||
|
||||
Matchers can also be combined with `And`, `Or` and `Not`:
|
||||
|
||||
```go
|
||||
|
||||
// Match if all match
|
||||
q.And(
|
||||
q.Gt("Age", 7),
|
||||
q.Re("Name", "^D")
|
||||
)
|
||||
|
||||
// Match if one matches
|
||||
q.Or(
|
||||
q.Re("Name", "^A"),
|
||||
q.Not(
|
||||
q.Re("Name", "^B")
|
||||
),
|
||||
q.Re("Name", "^C"),
|
||||
q.In("Group", []string{"Staff", "Admin"}),
|
||||
q.And(
|
||||
q.StrictEq("Password", []byte(password)),
|
||||
q.Eq("Registered", true)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
You can find the complete list in the [documentation](https://godoc.org/github.com/asdine/storm/q#Matcher).
|
||||
|
||||
`Select` takes any number of matchers and wraps them into a `q.And()` so it's not necessary to specify it. It returns a [`Query`](https://godoc.org/github.com/asdine/storm#Query) type.
|
||||
|
||||
```go
|
||||
query := db.Select(q.Gte("Age", 7), q.Lte("Age", 77))
|
||||
```
|
||||
|
||||
The `Query` type contains methods to filter and order the records.
|
||||
|
||||
```go
|
||||
// Limit
|
||||
query = query.Limit(10)
|
||||
|
||||
// Skip
|
||||
query = query.Skip(20)
|
||||
|
||||
// Calls can also be chained
|
||||
query = query.Limit(10).Skip(20).OrderBy("Age").Reverse()
|
||||
```
|
||||
|
||||
But also to specify how to fetch them.
|
||||
|
||||
```go
|
||||
var users []User
|
||||
err = query.Find(&users)
|
||||
|
||||
var user User
|
||||
err = query.First(&user)
|
||||
```
|
||||
|
||||
Examples with `Select`:
|
||||
|
||||
```go
|
||||
// Find all users with an ID between 10 and 100
|
||||
err = db.Select(q.Gte("ID", 10), q.Lte("ID", 100)).Find(&users)
|
||||
|
||||
// Nested matchers
|
||||
err = db.Select(q.Or(
|
||||
q.Gt("ID", 50),
|
||||
q.Lt("Age", 21),
|
||||
q.And(
|
||||
q.Eq("Group", "admin"),
|
||||
q.Gte("Age", 21),
|
||||
),
|
||||
)).Find(&users)
|
||||
|
||||
query := db.Select(q.Gte("ID", 10), q.Lte("ID", 100)).Limit(10).Skip(5).Reverse().OrderBy("Age", "Name")
|
||||
|
||||
// Find multiple records
|
||||
err = query.Find(&users)
|
||||
// or
|
||||
err = db.Select(q.Gte("ID", 10), q.Lte("ID", 100)).Limit(10).Skip(5).Reverse().OrderBy("Age", "Name").Find(&users)
|
||||
|
||||
// Find first record
|
||||
err = query.First(&user)
|
||||
// or
|
||||
err = db.Select(q.Gte("ID", 10), q.Lte("ID", 100)).Limit(10).Skip(5).Reverse().OrderBy("Age", "Name").First(&user)
|
||||
|
||||
// Delete all matching records
|
||||
err = query.Delete(new(User))
|
||||
|
||||
// Fetching records one by one (useful when the bucket contains a lot of records)
|
||||
query = db.Select(q.Gte("ID", 10),q.Lte("ID", 100)).OrderBy("Age", "Name")
|
||||
|
||||
err = query.Each(new(User), func(record interface{}) error) {
|
||||
u := record.(*User)
|
||||
...
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
See the [documentation](https://godoc.org/github.com/asdine/storm#Query) for a complete list of methods.
|
||||
|
||||
### Transactions
|
||||
|
||||
```go
|
||||
tx, err := db.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
accountA.Amount -= 100
|
||||
accountB.Amount += 100
|
||||
|
||||
err = tx.Save(accountA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Save(accountB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
Storm options are functions that can be passed when constructing you Storm instance. You can pass it any number of options.
|
||||
|
||||
#### BoltOptions
|
||||
|
||||
By default, Storm opens a database with the mode `0600` and a timeout of one second.
|
||||
You can change this behavior by using `BoltOptions`
|
||||
|
||||
```go
|
||||
db, err := storm.Open("my.db", storm.BoltOptions(0600, &bolt.Options{Timeout: 1 * time.Second}))
|
||||
```
|
||||
|
||||
#### MarshalUnmarshaler
|
||||
|
||||
To store the data in BoltDB, Storm marshals it in JSON by default. If you wish to change this behavior you can pass a codec that implements [`codec.MarshalUnmarshaler`](https://godoc.org/github.com/asdine/storm/codec#MarshalUnmarshaler) via the [`storm.Codec`](https://godoc.org/github.com/asdine/storm#Codec) option:
|
||||
|
||||
```go
|
||||
db := storm.Open("my.db", storm.Codec(myCodec))
|
||||
```
|
||||
|
||||
##### Provided Codecs
|
||||
|
||||
You can easily implement your own `MarshalUnmarshaler`, but Storm comes with built-in support for [JSON](https://godoc.org/github.com/asdine/storm/codec/json) (default), [GOB](https://godoc.org/github.com/asdine/storm/codec/gob), [Sereal](https://godoc.org/github.com/asdine/storm/codec/sereal), [Protocol Buffers](https://godoc.org/github.com/asdine/storm/codec/protobuf) and [MessagePack](https://godoc.org/github.com/asdine/storm/codec/msgpack).
|
||||
|
||||
These can be used by importing the relevant package and use that codec to configure Storm. The example below shows all variants (without proper error handling):
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/asdine/storm/v3"
|
||||
"github.com/asdine/storm/v3/codec/gob"
|
||||
"github.com/asdine/storm/v3/codec/json"
|
||||
"github.com/asdine/storm/v3/codec/sereal"
|
||||
"github.com/asdine/storm/v3/codec/protobuf"
|
||||
"github.com/asdine/storm/v3/codec/msgpack"
|
||||
)
|
||||
|
||||
var gobDb, _ = storm.Open("gob.db", storm.Codec(gob.Codec))
|
||||
var jsonDb, _ = storm.Open("json.db", storm.Codec(json.Codec))
|
||||
var serealDb, _ = storm.Open("sereal.db", storm.Codec(sereal.Codec))
|
||||
var protobufDb, _ = storm.Open("protobuf.db", storm.Codec(protobuf.Codec))
|
||||
var msgpackDb, _ = storm.Open("msgpack.db", storm.Codec(msgpack.Codec))
|
||||
```
|
||||
|
||||
**Tip**: Adding Storm tags to generated Protobuf files can be tricky. A good solution is to use [this tool](https://github.com/favadi/protoc-go-inject-tag) to inject the tags during the compilation.
|
||||
|
||||
#### Use existing Bolt connection
|
||||
|
||||
You can use an existing connection and pass it to Storm
|
||||
|
||||
```go
|
||||
bDB, _ := bolt.Open(filepath.Join(dir, "bolt.db"), 0600, &bolt.Options{Timeout: 10 * time.Second})
|
||||
db := storm.Open("my.db", storm.UseDB(bDB))
|
||||
```
|
||||
|
||||
#### Batch mode
|
||||
|
||||
Batch mode can be enabled to speed up concurrent writes (see [Batch read-write transactions](https://github.com/coreos/bbolt#batch-read-write-transactions))
|
||||
|
||||
```go
|
||||
db := storm.Open("my.db", storm.Batch())
|
||||
```
|
||||
|
||||
## Nodes and nested buckets
|
||||
|
||||
Storm takes advantage of BoltDB nested buckets feature by using `storm.Node`.
|
||||
A `storm.Node` is the underlying object used by `storm.DB` to manipulate a bucket.
|
||||
To create a nested bucket and use the same API as `storm.DB`, you can use the `DB.From` method.
|
||||
|
||||
```go
|
||||
repo := db.From("repo")
|
||||
|
||||
err := repo.Save(&Issue{
|
||||
Title: "I want more features",
|
||||
Author: user.ID,
|
||||
})
|
||||
|
||||
err = repo.Save(newRelease("0.10"))
|
||||
|
||||
var issues []Issue
|
||||
err = repo.Find("Author", user.ID, &issues)
|
||||
|
||||
var release Release
|
||||
err = repo.One("Tag", "0.10", &release)
|
||||
```
|
||||
|
||||
You can also chain the nodes to create a hierarchy
|
||||
|
||||
```go
|
||||
chars := db.From("characters")
|
||||
heroes := chars.From("heroes")
|
||||
enemies := chars.From("enemies")
|
||||
|
||||
items := db.From("items")
|
||||
potions := items.From("consumables").From("medicine").From("potions")
|
||||
```
|
||||
|
||||
You can even pass the entire hierarchy as arguments to `From`:
|
||||
|
||||
```go
|
||||
privateNotes := db.From("notes", "private")
|
||||
workNotes := db.From("notes", "work")
|
||||
```
|
||||
|
||||
### Node options
|
||||
|
||||
A Node can also be configured. Activating an option on a Node creates a copy, so a Node is always thread-safe.
|
||||
|
||||
```go
|
||||
n := db.From("my-node")
|
||||
```
|
||||
|
||||
Give a bolt.Tx transaction to the Node
|
||||
|
||||
```go
|
||||
n = n.WithTransaction(tx)
|
||||
```
|
||||
|
||||
Enable batch mode
|
||||
|
||||
```go
|
||||
n = n.WithBatch(true)
|
||||
```
|
||||
|
||||
Use a Codec
|
||||
|
||||
```go
|
||||
n = n.WithCodec(gob.Codec)
|
||||
```
|
||||
|
||||
## Simple Key/Value store
|
||||
|
||||
Storm can be used as a simple, robust, key/value store that can store anything.
|
||||
The key and the value can be of any type as long as the key is not a zero value.
|
||||
|
||||
Saving data :
|
||||
|
||||
```go
|
||||
db.Set("logs", time.Now(), "I'm eating my breakfast man")
|
||||
db.Set("sessions", bson.NewObjectId(), &someUser)
|
||||
db.Set("weird storage", "754-3010", map[string]interface{}{
|
||||
"hair": "blonde",
|
||||
"likes": []string{"cheese", "star wars"},
|
||||
})
|
||||
```
|
||||
|
||||
Fetching data :
|
||||
|
||||
```go
|
||||
user := User{}
|
||||
db.Get("sessions", someObjectId, &user)
|
||||
|
||||
var details map[string]interface{}
|
||||
db.Get("weird storage", "754-3010", &details)
|
||||
|
||||
db.Get("sessions", someObjectId, &details)
|
||||
```
|
||||
|
||||
Deleting data :
|
||||
|
||||
```go
|
||||
db.Delete("sessions", someObjectId)
|
||||
db.Delete("weird storage", "754-3010")
|
||||
```
|
||||
|
||||
You can find other useful methods in the [documentation](https://godoc.org/github.com/asdine/storm#KeyValueStore).
|
||||
|
||||
## BoltDB
|
||||
|
||||
BoltDB is still easily accessible and can be used as usual
|
||||
|
||||
```go
|
||||
db.Bolt.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte("my bucket"))
|
||||
val := bucket.Get([]byte("any id"))
|
||||
fmt.Println(string(val))
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
A transaction can be also be passed to Storm
|
||||
|
||||
```go
|
||||
db.Bolt.Update(func(tx *bolt.Tx) error {
|
||||
...
|
||||
dbx := db.WithTransaction(tx)
|
||||
err = dbx.Save(&user)
|
||||
...
|
||||
return nil
|
||||
})
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Credits
|
||||
|
||||
- [Asdine El Hrychy](https://github.com/asdine)
|
||||
- [Bjørn Erik Pedersen](https://github.com/bep)
|
47
vendor/github.com/asdine/storm/v3/bucket.go
generated
vendored
Normal file
47
vendor/github.com/asdine/storm/v3/bucket.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package storm
|
||||
|
||||
import bolt "go.etcd.io/bbolt"
|
||||
|
||||
// CreateBucketIfNotExists creates the bucket below the current node if it doesn't
|
||||
// already exist.
|
||||
func (n *node) CreateBucketIfNotExists(tx *bolt.Tx, bucket string) (*bolt.Bucket, error) {
|
||||
var b *bolt.Bucket
|
||||
var err error
|
||||
|
||||
bucketNames := append(n.rootBucket, bucket)
|
||||
|
||||
for _, bucketName := range bucketNames {
|
||||
if b != nil {
|
||||
if b, err = b.CreateBucketIfNotExists([]byte(bucketName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
if b, err = tx.CreateBucketIfNotExists([]byte(bucketName)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// GetBucket returns the given bucket below the current node.
|
||||
func (n *node) GetBucket(tx *bolt.Tx, children ...string) *bolt.Bucket {
|
||||
var b *bolt.Bucket
|
||||
|
||||
bucketNames := append(n.rootBucket, children...)
|
||||
for _, bucketName := range bucketNames {
|
||||
if b != nil {
|
||||
if b = b.Bucket([]byte(bucketName)); b == nil {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if b = tx.Bucket([]byte(bucketName)); b == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
1
vendor/github.com/asdine/storm/v3/codec/.gitignore
generated
vendored
Normal file
1
vendor/github.com/asdine/storm/v3/codec/.gitignore
generated
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.db
|
11
vendor/github.com/asdine/storm/v3/codec/codec.go
generated
vendored
Normal file
11
vendor/github.com/asdine/storm/v3/codec/codec.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Package codec contains sub-packages with different codecs that can be used
|
||||
// to encode and decode entities in Storm.
|
||||
package codec
|
||||
|
||||
// MarshalUnmarshaler represents a codec used to marshal and unmarshal entities.
|
||||
type MarshalUnmarshaler interface {
|
||||
Marshal(v interface{}) ([]byte, error)
|
||||
Unmarshal(b []byte, v interface{}) error
|
||||
// name of this codec
|
||||
Name() string
|
||||
}
|
25
vendor/github.com/asdine/storm/v3/codec/json/json.go
generated
vendored
Normal file
25
vendor/github.com/asdine/storm/v3/codec/json/json.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// Package json contains a codec to encode and decode entities in JSON format
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const name = "json"
|
||||
|
||||
// Codec that encodes to and decodes from JSON.
|
||||
var Codec = new(jsonCodec)
|
||||
|
||||
type jsonCodec int
|
||||
|
||||
func (j jsonCodec) Marshal(v interface{}) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
func (j jsonCodec) Unmarshal(b []byte, v interface{}) error {
|
||||
return json.Unmarshal(b, v)
|
||||
}
|
||||
|
||||
func (j jsonCodec) Name() string {
|
||||
return name
|
||||
}
|
51
vendor/github.com/asdine/storm/v3/errors.go
generated
vendored
Normal file
51
vendor/github.com/asdine/storm/v3/errors.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package storm
|
||||
|
||||
import "errors"
|
||||
|
||||
// Errors
|
||||
var (
|
||||
// ErrNoID is returned when no ID field or id tag is found in the struct.
|
||||
ErrNoID = errors.New("missing struct tag id or ID field")
|
||||
|
||||
// ErrZeroID is returned when the ID field is a zero value.
|
||||
ErrZeroID = errors.New("id field must not be a zero value")
|
||||
|
||||
// ErrBadType is returned when a method receives an unexpected value type.
|
||||
ErrBadType = errors.New("provided data must be a struct or a pointer to struct")
|
||||
|
||||
// ErrAlreadyExists is returned uses when trying to set an existing value on a field that has a unique index.
|
||||
ErrAlreadyExists = errors.New("already exists")
|
||||
|
||||
// ErrNilParam is returned when the specified param is expected to be not nil.
|
||||
ErrNilParam = errors.New("param must not be nil")
|
||||
|
||||
// ErrUnknownTag is returned when an unexpected tag is specified.
|
||||
ErrUnknownTag = errors.New("unknown tag")
|
||||
|
||||
// ErrIdxNotFound is returned when the specified index is not found.
|
||||
ErrIdxNotFound = errors.New("index not found")
|
||||
|
||||
// ErrSlicePtrNeeded is returned when an unexpected value is given, instead of a pointer to slice.
|
||||
ErrSlicePtrNeeded = errors.New("provided target must be a pointer to slice")
|
||||
|
||||
// ErrStructPtrNeeded is returned when an unexpected value is given, instead of a pointer to struct.
|
||||
ErrStructPtrNeeded = errors.New("provided target must be a pointer to struct")
|
||||
|
||||
// ErrPtrNeeded is returned when an unexpected value is given, instead of a pointer.
|
||||
ErrPtrNeeded = errors.New("provided target must be a pointer to a valid variable")
|
||||
|
||||
// ErrNoName is returned when the specified struct has no name.
|
||||
ErrNoName = errors.New("provided target must have a name")
|
||||
|
||||
// ErrNotFound is returned when the specified record is not saved in the bucket.
|
||||
ErrNotFound = errors.New("not found")
|
||||
|
||||
// ErrNotInTransaction is returned when trying to rollback or commit when not in transaction.
|
||||
ErrNotInTransaction = errors.New("not in transaction")
|
||||
|
||||
// ErrIncompatibleValue is returned when trying to set a value with a different type than the chosen field
|
||||
ErrIncompatibleValue = errors.New("incompatible value")
|
||||
|
||||
// ErrDifferentCodec is returned when using a codec different than the first codec used with the bucket.
|
||||
ErrDifferentCodec = errors.New("the selected codec is incompatible with this bucket")
|
||||
)
|
226
vendor/github.com/asdine/storm/v3/extract.go
generated
vendored
Normal file
226
vendor/github.com/asdine/storm/v3/extract.go
generated
vendored
Normal file
@ -0,0 +1,226 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/asdine/storm/v3/index"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// Storm tags
|
||||
const (
|
||||
tagID = "id"
|
||||
tagIdx = "index"
|
||||
tagUniqueIdx = "unique"
|
||||
tagInline = "inline"
|
||||
tagIncrement = "increment"
|
||||
indexPrefix = "__storm_index_"
|
||||
)
|
||||
|
||||
type fieldConfig struct {
|
||||
Name string
|
||||
Index string
|
||||
IsZero bool
|
||||
IsID bool
|
||||
Increment bool
|
||||
IncrementStart int64
|
||||
IsInteger bool
|
||||
Value *reflect.Value
|
||||
ForceUpdate bool
|
||||
}
|
||||
|
||||
// structConfig is a structure gathering all the relevant informations about a model
|
||||
type structConfig struct {
|
||||
Name string
|
||||
Fields map[string]*fieldConfig
|
||||
ID *fieldConfig
|
||||
}
|
||||
|
||||
func extract(s *reflect.Value, mi ...*structConfig) (*structConfig, error) {
|
||||
if s.Kind() == reflect.Ptr {
|
||||
e := s.Elem()
|
||||
s = &e
|
||||
}
|
||||
if s.Kind() != reflect.Struct {
|
||||
return nil, ErrBadType
|
||||
}
|
||||
|
||||
typ := s.Type()
|
||||
|
||||
var child bool
|
||||
|
||||
var m *structConfig
|
||||
if len(mi) > 0 {
|
||||
m = mi[0]
|
||||
child = true
|
||||
} else {
|
||||
m = &structConfig{}
|
||||
m.Fields = make(map[string]*fieldConfig)
|
||||
}
|
||||
|
||||
if m.Name == "" {
|
||||
m.Name = typ.Name()
|
||||
}
|
||||
|
||||
numFields := s.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
field := typ.Field(i)
|
||||
value := s.Field(i)
|
||||
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
err := extractField(&value, &field, m, child)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if child {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
if m.ID == nil {
|
||||
return nil, ErrNoID
|
||||
}
|
||||
|
||||
if m.Name == "" {
|
||||
return nil, ErrNoName
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func extractField(value *reflect.Value, field *reflect.StructField, m *structConfig, isChild bool) error {
|
||||
var f *fieldConfig
|
||||
var err error
|
||||
|
||||
tag := field.Tag.Get("storm")
|
||||
if tag != "" {
|
||||
f = &fieldConfig{
|
||||
Name: field.Name,
|
||||
IsZero: isZero(value),
|
||||
IsInteger: isInteger(value),
|
||||
Value: value,
|
||||
IncrementStart: 1,
|
||||
}
|
||||
|
||||
tags := strings.Split(tag, ",")
|
||||
|
||||
for _, tag := range tags {
|
||||
switch tag {
|
||||
case "id":
|
||||
f.IsID = true
|
||||
f.Index = tagUniqueIdx
|
||||
case tagUniqueIdx, tagIdx:
|
||||
f.Index = tag
|
||||
case tagInline:
|
||||
if value.Kind() == reflect.Ptr {
|
||||
e := value.Elem()
|
||||
value = &e
|
||||
}
|
||||
if value.Kind() == reflect.Struct {
|
||||
a := value.Addr()
|
||||
_, err := extract(&a, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// we don't need to save this field
|
||||
return nil
|
||||
default:
|
||||
if strings.HasPrefix(tag, tagIncrement) {
|
||||
f.Increment = true
|
||||
parts := strings.Split(tag, "=")
|
||||
if parts[0] != tagIncrement {
|
||||
return ErrUnknownTag
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
f.IncrementStart, err = strconv.ParseInt(parts[1], 0, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return ErrUnknownTag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := m.Fields[f.Name]; !ok || !isChild {
|
||||
m.Fields[f.Name] = f
|
||||
}
|
||||
}
|
||||
|
||||
if m.ID == nil && f != nil && f.IsID {
|
||||
m.ID = f
|
||||
}
|
||||
|
||||
// the field is named ID and no ID field has been detected before
|
||||
if m.ID == nil && field.Name == "ID" {
|
||||
if f == nil {
|
||||
f = &fieldConfig{
|
||||
Index: tagUniqueIdx,
|
||||
Name: field.Name,
|
||||
IsZero: isZero(value),
|
||||
IsInteger: isInteger(value),
|
||||
IsID: true,
|
||||
Value: value,
|
||||
IncrementStart: 1,
|
||||
}
|
||||
m.Fields[field.Name] = f
|
||||
}
|
||||
m.ID = f
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractSingleField(ref *reflect.Value, fieldName string) (*structConfig, error) {
|
||||
var cfg structConfig
|
||||
cfg.Fields = make(map[string]*fieldConfig)
|
||||
|
||||
f, ok := ref.Type().FieldByName(fieldName)
|
||||
if !ok || f.PkgPath != "" {
|
||||
return nil, fmt.Errorf("field %s not found", fieldName)
|
||||
}
|
||||
|
||||
v := ref.FieldByName(fieldName)
|
||||
err := extractField(&v, &f, &cfg, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func getIndex(bucket *bolt.Bucket, idxKind string, fieldName string) (index.Index, error) {
|
||||
var idx index.Index
|
||||
var err error
|
||||
|
||||
switch idxKind {
|
||||
case tagUniqueIdx:
|
||||
idx, err = index.NewUniqueIndex(bucket, []byte(indexPrefix+fieldName))
|
||||
case tagIdx:
|
||||
idx, err = index.NewListIndex(bucket, []byte(indexPrefix+fieldName))
|
||||
default:
|
||||
err = ErrIdxNotFound
|
||||
}
|
||||
|
||||
return idx, err
|
||||
}
|
||||
|
||||
func isZero(v *reflect.Value) bool {
|
||||
zero := reflect.Zero(v.Type()).Interface()
|
||||
current := v.Interface()
|
||||
return reflect.DeepEqual(current, zero)
|
||||
}
|
||||
|
||||
func isInteger(v *reflect.Value) bool {
|
||||
kind := v.Kind()
|
||||
return v != nil && kind >= reflect.Int && kind <= reflect.Uint64
|
||||
}
|
499
vendor/github.com/asdine/storm/v3/finder.go
generated
vendored
Normal file
499
vendor/github.com/asdine/storm/v3/finder.go
generated
vendored
Normal file
@ -0,0 +1,499 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/asdine/storm/v3/index"
|
||||
"github.com/asdine/storm/v3/q"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// A Finder can fetch types from BoltDB.
|
||||
type Finder interface {
|
||||
// One returns one record by the specified index
|
||||
One(fieldName string, value interface{}, to interface{}) error
|
||||
|
||||
// Find returns one or more records by the specified index
|
||||
Find(fieldName string, value interface{}, to interface{}, options ...func(q *index.Options)) error
|
||||
|
||||
// AllByIndex gets all the records of a bucket that are indexed in the specified index
|
||||
AllByIndex(fieldName string, to interface{}, options ...func(*index.Options)) error
|
||||
|
||||
// All gets all the records of a bucket.
|
||||
// If there are no records it returns no error and the 'to' parameter is set to an empty slice.
|
||||
All(to interface{}, options ...func(*index.Options)) error
|
||||
|
||||
// Select a list of records that match a list of matchers. Doesn't use indexes.
|
||||
Select(matchers ...q.Matcher) Query
|
||||
|
||||
// Range returns one or more records by the specified index within the specified range
|
||||
Range(fieldName string, min, max, to interface{}, options ...func(*index.Options)) error
|
||||
|
||||
// Prefix returns one or more records whose given field starts with the specified prefix.
|
||||
Prefix(fieldName string, prefix string, to interface{}, options ...func(*index.Options)) error
|
||||
|
||||
// Count counts all the records of a bucket
|
||||
Count(data interface{}) (int, error)
|
||||
}
|
||||
|
||||
// One returns one record by the specified index
|
||||
func (n *node) One(fieldName string, value interface{}, to interface{}) error {
|
||||
sink, err := newFirstSink(n, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucketName := sink.bucketName()
|
||||
if bucketName == "" {
|
||||
return ErrNoName
|
||||
}
|
||||
|
||||
if fieldName == "" {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
ref := reflect.Indirect(sink.ref)
|
||||
cfg, err := extractSingleField(&ref, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field, ok := cfg.Fields[fieldName]
|
||||
if !ok || (!field.IsID && field.Index == "") {
|
||||
query := newQuery(n, q.StrictEq(fieldName, value))
|
||||
query.Limit(1)
|
||||
|
||||
if n.tx != nil {
|
||||
err = query.query(n.tx, sink)
|
||||
} else {
|
||||
err = n.s.Bolt.View(func(tx *bolt.Tx) error {
|
||||
return query.query(tx, sink)
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sink.flush()
|
||||
}
|
||||
|
||||
val, err := toBytes(value, n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readTx(func(tx *bolt.Tx) error {
|
||||
return n.one(tx, bucketName, fieldName, cfg, to, val, field.IsID)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) one(tx *bolt.Tx, bucketName, fieldName string, cfg *structConfig, to interface{}, val []byte, skipIndex bool) error {
|
||||
bucket := n.GetBucket(tx, bucketName)
|
||||
if bucket == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
var id []byte
|
||||
if !skipIndex {
|
||||
idx, err := getIndex(bucket, cfg.Fields[fieldName].Index, fieldName)
|
||||
if err != nil {
|
||||
if err == index.ErrNotFound {
|
||||
return ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
id = idx.Get(val)
|
||||
} else {
|
||||
id = val
|
||||
}
|
||||
|
||||
if id == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
raw := bucket.Get(id)
|
||||
if raw == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return n.codec.Unmarshal(raw, to)
|
||||
}
|
||||
|
||||
// Find returns one or more records by the specified index
|
||||
func (n *node) Find(fieldName string, value interface{}, to interface{}, options ...func(q *index.Options)) error {
|
||||
sink, err := newListSink(n, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bucketName := sink.bucketName()
|
||||
if bucketName == "" {
|
||||
return ErrNoName
|
||||
}
|
||||
|
||||
ref := reflect.Indirect(reflect.New(sink.elemType))
|
||||
cfg, err := extractSingleField(&ref, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := index.NewOptions()
|
||||
for _, fn := range options {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
field, ok := cfg.Fields[fieldName]
|
||||
if !ok || (!field.IsID && (field.Index == "" || value == nil)) {
|
||||
query := newQuery(n, q.Eq(fieldName, value))
|
||||
query.Skip(opts.Skip).Limit(opts.Limit)
|
||||
|
||||
if opts.Reverse {
|
||||
query.Reverse()
|
||||
}
|
||||
|
||||
err = n.readTx(func(tx *bolt.Tx) error {
|
||||
return query.query(tx, sink)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sink.flush()
|
||||
}
|
||||
|
||||
val, err := toBytes(value, n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readTx(func(tx *bolt.Tx) error {
|
||||
return n.find(tx, bucketName, fieldName, cfg, sink, val, opts)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) find(tx *bolt.Tx, bucketName, fieldName string, cfg *structConfig, sink *listSink, val []byte, opts *index.Options) error {
|
||||
bucket := n.GetBucket(tx, bucketName)
|
||||
if bucket == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
idx, err := getIndex(bucket, cfg.Fields[fieldName].Index, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, err := idx.All(val, opts)
|
||||
if err != nil {
|
||||
if err == index.ErrNotFound {
|
||||
return ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
sink.results = reflect.MakeSlice(reflect.Indirect(sink.ref).Type(), len(list), len(list))
|
||||
|
||||
sorter := newSorter(n, sink)
|
||||
for i := range list {
|
||||
raw := bucket.Get(list[i])
|
||||
if raw == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
if _, err := sorter.filter(nil, bucket, list[i], raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sorter.flush()
|
||||
}
|
||||
|
||||
// AllByIndex gets all the records of a bucket that are indexed in the specified index
|
||||
func (n *node) AllByIndex(fieldName string, to interface{}, options ...func(*index.Options)) error {
|
||||
if fieldName == "" {
|
||||
return n.All(to, options...)
|
||||
}
|
||||
|
||||
ref := reflect.ValueOf(to)
|
||||
|
||||
if ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Slice {
|
||||
return ErrSlicePtrNeeded
|
||||
}
|
||||
|
||||
typ := reflect.Indirect(ref).Type().Elem()
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
}
|
||||
|
||||
newElem := reflect.New(typ)
|
||||
|
||||
cfg, err := extract(&newElem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ID.Name == fieldName {
|
||||
return n.All(to, options...)
|
||||
}
|
||||
|
||||
opts := index.NewOptions()
|
||||
for _, fn := range options {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
return n.readTx(func(tx *bolt.Tx) error {
|
||||
return n.allByIndex(tx, fieldName, cfg, &ref, opts)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) allByIndex(tx *bolt.Tx, fieldName string, cfg *structConfig, ref *reflect.Value, opts *index.Options) error {
|
||||
bucket := n.GetBucket(tx, cfg.Name)
|
||||
if bucket == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
fieldCfg, ok := cfg.Fields[fieldName]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
idx, err := getIndex(bucket, fieldCfg.Index, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, err := idx.AllRecords(opts)
|
||||
if err != nil {
|
||||
if err == index.ErrNotFound {
|
||||
return ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
results := reflect.MakeSlice(reflect.Indirect(*ref).Type(), len(list), len(list))
|
||||
|
||||
for i := range list {
|
||||
raw := bucket.Get(list[i])
|
||||
if raw == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
err = n.codec.Unmarshal(raw, results.Index(i).Addr().Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
reflect.Indirect(*ref).Set(results)
|
||||
return nil
|
||||
}
|
||||
|
||||
// All gets all the records of a bucket.
|
||||
// If there are no records it returns no error and the 'to' parameter is set to an empty slice.
|
||||
func (n *node) All(to interface{}, options ...func(*index.Options)) error {
|
||||
opts := index.NewOptions()
|
||||
for _, fn := range options {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
query := newQuery(n, nil).Limit(opts.Limit).Skip(opts.Skip)
|
||||
if opts.Reverse {
|
||||
query.Reverse()
|
||||
}
|
||||
|
||||
err := query.Find(to)
|
||||
if err != nil && err != ErrNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if err == ErrNotFound {
|
||||
ref := reflect.ValueOf(to)
|
||||
results := reflect.MakeSlice(reflect.Indirect(ref).Type(), 0, 0)
|
||||
reflect.Indirect(ref).Set(results)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Range returns one or more records by the specified index within the specified range
|
||||
func (n *node) Range(fieldName string, min, max, to interface{}, options ...func(*index.Options)) error {
|
||||
sink, err := newListSink(n, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucketName := sink.bucketName()
|
||||
if bucketName == "" {
|
||||
return ErrNoName
|
||||
}
|
||||
|
||||
ref := reflect.Indirect(reflect.New(sink.elemType))
|
||||
cfg, err := extractSingleField(&ref, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := index.NewOptions()
|
||||
for _, fn := range options {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
field, ok := cfg.Fields[fieldName]
|
||||
if !ok || (!field.IsID && field.Index == "") {
|
||||
query := newQuery(n, q.And(q.Gte(fieldName, min), q.Lte(fieldName, max)))
|
||||
query.Skip(opts.Skip).Limit(opts.Limit)
|
||||
|
||||
if opts.Reverse {
|
||||
query.Reverse()
|
||||
}
|
||||
|
||||
err = n.readTx(func(tx *bolt.Tx) error {
|
||||
return query.query(tx, sink)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sink.flush()
|
||||
}
|
||||
|
||||
mn, err := toBytes(min, n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mx, err := toBytes(max, n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readTx(func(tx *bolt.Tx) error {
|
||||
return n.rnge(tx, bucketName, fieldName, cfg, sink, mn, mx, opts)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) rnge(tx *bolt.Tx, bucketName, fieldName string, cfg *structConfig, sink *listSink, min, max []byte, opts *index.Options) error {
|
||||
bucket := n.GetBucket(tx, bucketName)
|
||||
if bucket == nil {
|
||||
reflect.Indirect(sink.ref).SetLen(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
idx, err := getIndex(bucket, cfg.Fields[fieldName].Index, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, err := idx.Range(min, max, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sink.results = reflect.MakeSlice(reflect.Indirect(sink.ref).Type(), len(list), len(list))
|
||||
sorter := newSorter(n, sink)
|
||||
for i := range list {
|
||||
raw := bucket.Get(list[i])
|
||||
if raw == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
if _, err := sorter.filter(nil, bucket, list[i], raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sorter.flush()
|
||||
}
|
||||
|
||||
// Prefix returns one or more records whose given field starts with the specified prefix.
|
||||
func (n *node) Prefix(fieldName string, prefix string, to interface{}, options ...func(*index.Options)) error {
|
||||
sink, err := newListSink(n, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucketName := sink.bucketName()
|
||||
if bucketName == "" {
|
||||
return ErrNoName
|
||||
}
|
||||
|
||||
ref := reflect.Indirect(reflect.New(sink.elemType))
|
||||
cfg, err := extractSingleField(&ref, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := index.NewOptions()
|
||||
for _, fn := range options {
|
||||
fn(opts)
|
||||
}
|
||||
|
||||
field, ok := cfg.Fields[fieldName]
|
||||
if !ok || (!field.IsID && field.Index == "") {
|
||||
query := newQuery(n, q.Re(fieldName, fmt.Sprintf("^%s", prefix)))
|
||||
query.Skip(opts.Skip).Limit(opts.Limit)
|
||||
|
||||
if opts.Reverse {
|
||||
query.Reverse()
|
||||
}
|
||||
|
||||
err = n.readTx(func(tx *bolt.Tx) error {
|
||||
return query.query(tx, sink)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sink.flush()
|
||||
}
|
||||
|
||||
prfx, err := toBytes(prefix, n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readTx(func(tx *bolt.Tx) error {
|
||||
return n.prefix(tx, bucketName, fieldName, cfg, sink, prfx, opts)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) prefix(tx *bolt.Tx, bucketName, fieldName string, cfg *structConfig, sink *listSink, prefix []byte, opts *index.Options) error {
|
||||
bucket := n.GetBucket(tx, bucketName)
|
||||
if bucket == nil {
|
||||
reflect.Indirect(sink.ref).SetLen(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
idx, err := getIndex(bucket, cfg.Fields[fieldName].Index, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
list, err := idx.Prefix(prefix, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sink.results = reflect.MakeSlice(reflect.Indirect(sink.ref).Type(), len(list), len(list))
|
||||
sorter := newSorter(n, sink)
|
||||
for i := range list {
|
||||
raw := bucket.Get(list[i])
|
||||
if raw == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
if _, err := sorter.filter(nil, bucket, list[i], raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sorter.flush()
|
||||
}
|
||||
|
||||
// Count counts all the records of a bucket
|
||||
func (n *node) Count(data interface{}) (int, error) {
|
||||
return n.Select().Count(data)
|
||||
}
|
14
vendor/github.com/asdine/storm/v3/index/errors.go
generated
vendored
Normal file
14
vendor/github.com/asdine/storm/v3/index/errors.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package index
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrNotFound is returned when the specified record is not saved in the bucket.
|
||||
ErrNotFound = errors.New("not found")
|
||||
|
||||
// ErrAlreadyExists is returned uses when trying to set an existing value on a field that has a unique index.
|
||||
ErrAlreadyExists = errors.New("already exists")
|
||||
|
||||
// ErrNilParam is returned when the specified param is expected to be not nil.
|
||||
ErrNilParam = errors.New("param must not be nil")
|
||||
)
|
14
vendor/github.com/asdine/storm/v3/index/indexes.go
generated
vendored
Normal file
14
vendor/github.com/asdine/storm/v3/index/indexes.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// Package index contains Index engines used to store values and their corresponding IDs
|
||||
package index
|
||||
|
||||
// Index interface
|
||||
type Index interface {
|
||||
Add(value []byte, targetID []byte) error
|
||||
Remove(value []byte) error
|
||||
RemoveID(id []byte) error
|
||||
Get(value []byte) []byte
|
||||
All(value []byte, opts *Options) ([][]byte, error)
|
||||
AllRecords(opts *Options) ([][]byte, error)
|
||||
Range(min []byte, max []byte, opts *Options) ([][]byte, error)
|
||||
Prefix(prefix []byte, opts *Options) ([][]byte, error)
|
||||
}
|
283
vendor/github.com/asdine/storm/v3/index/list.go
generated
vendored
Normal file
283
vendor/github.com/asdine/storm/v3/index/list.go
generated
vendored
Normal file
@ -0,0 +1,283 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/asdine/storm/v3/internal"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// NewListIndex loads a ListIndex
|
||||
func NewListIndex(parent *bolt.Bucket, indexName []byte) (*ListIndex, error) {
|
||||
var err error
|
||||
b := parent.Bucket(indexName)
|
||||
if b == nil {
|
||||
if !parent.Writable() {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
b, err = parent.CreateBucket(indexName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
ids, err := NewUniqueIndex(b, []byte("storm__ids"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ListIndex{
|
||||
IndexBucket: b,
|
||||
Parent: parent,
|
||||
IDs: ids,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListIndex is an index that references values and the corresponding IDs.
|
||||
type ListIndex struct {
|
||||
Parent *bolt.Bucket
|
||||
IndexBucket *bolt.Bucket
|
||||
IDs *UniqueIndex
|
||||
}
|
||||
|
||||
// Add a value to the list index
|
||||
func (idx *ListIndex) Add(newValue []byte, targetID []byte) error {
|
||||
if newValue == nil || len(newValue) == 0 {
|
||||
return ErrNilParam
|
||||
}
|
||||
if targetID == nil || len(targetID) == 0 {
|
||||
return ErrNilParam
|
||||
}
|
||||
|
||||
key := idx.IDs.Get(targetID)
|
||||
if key != nil {
|
||||
err := idx.IndexBucket.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = idx.IDs.Remove(targetID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key = key[:0]
|
||||
}
|
||||
|
||||
key = append(key, newValue...)
|
||||
key = append(key, '_')
|
||||
key = append(key, '_')
|
||||
key = append(key, targetID...)
|
||||
|
||||
err := idx.IDs.Add(targetID, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return idx.IndexBucket.Put(key, targetID)
|
||||
}
|
||||
|
||||
// Remove a value from the unique index
|
||||
func (idx *ListIndex) Remove(value []byte) error {
|
||||
var err error
|
||||
var keys [][]byte
|
||||
|
||||
c := idx.IndexBucket.Cursor()
|
||||
prefix := generatePrefix(value)
|
||||
|
||||
for k, _ := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, _ = c.Next() {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
err = idx.IndexBucket.Delete(k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return idx.IDs.RemoveID(value)
|
||||
}
|
||||
|
||||
// RemoveID removes an ID from the list index
|
||||
func (idx *ListIndex) RemoveID(targetID []byte) error {
|
||||
value := idx.IDs.Get(targetID)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := idx.IndexBucket.Delete(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return idx.IDs.Remove(targetID)
|
||||
}
|
||||
|
||||
// Get the first ID corresponding to the given value
|
||||
func (idx *ListIndex) Get(value []byte) []byte {
|
||||
c := idx.IndexBucket.Cursor()
|
||||
prefix := generatePrefix(value)
|
||||
|
||||
for k, id := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, id = c.Next() {
|
||||
return id
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// All the IDs corresponding to the given value
|
||||
func (idx *ListIndex) All(value []byte, opts *Options) ([][]byte, error) {
|
||||
var list [][]byte
|
||||
c := idx.IndexBucket.Cursor()
|
||||
cur := internal.Cursor{C: c, Reverse: opts != nil && opts.Reverse}
|
||||
|
||||
prefix := generatePrefix(value)
|
||||
|
||||
k, id := c.Seek(prefix)
|
||||
if cur.Reverse {
|
||||
var count int
|
||||
kc := k
|
||||
idc := id
|
||||
for ; kc != nil && bytes.HasPrefix(kc, prefix); kc, idc = c.Next() {
|
||||
count++
|
||||
k, id = kc, idc
|
||||
}
|
||||
if kc != nil {
|
||||
k, id = c.Prev()
|
||||
}
|
||||
list = make([][]byte, 0, count)
|
||||
}
|
||||
|
||||
for ; bytes.HasPrefix(k, prefix); k, id = cur.Next() {
|
||||
if opts != nil && opts.Skip > 0 {
|
||||
opts.Skip--
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit > 0 {
|
||||
opts.Limit--
|
||||
}
|
||||
|
||||
list = append(list, id)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// AllRecords returns all the IDs of this index
|
||||
func (idx *ListIndex) AllRecords(opts *Options) ([][]byte, error) {
|
||||
var list [][]byte
|
||||
|
||||
c := internal.Cursor{C: idx.IndexBucket.Cursor(), Reverse: opts != nil && opts.Reverse}
|
||||
|
||||
for k, id := c.First(); k != nil; k, id = c.Next() {
|
||||
if id == nil || bytes.Equal(k, []byte("storm__ids")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Skip > 0 {
|
||||
opts.Skip--
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit > 0 {
|
||||
opts.Limit--
|
||||
}
|
||||
|
||||
list = append(list, id)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Range returns the ids corresponding to the given range of values
|
||||
func (idx *ListIndex) Range(min []byte, max []byte, opts *Options) ([][]byte, error) {
|
||||
var list [][]byte
|
||||
|
||||
c := internal.RangeCursor{
|
||||
C: idx.IndexBucket.Cursor(),
|
||||
Reverse: opts != nil && opts.Reverse,
|
||||
Min: min,
|
||||
Max: max,
|
||||
CompareFn: func(val, limit []byte) int {
|
||||
pos := bytes.LastIndex(val, []byte("__"))
|
||||
return bytes.Compare(val[:pos], limit)
|
||||
},
|
||||
}
|
||||
|
||||
for k, id := c.First(); c.Continue(k); k, id = c.Next() {
|
||||
if id == nil || bytes.Equal(k, []byte("storm__ids")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Skip > 0 {
|
||||
opts.Skip--
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit > 0 {
|
||||
opts.Limit--
|
||||
}
|
||||
|
||||
list = append(list, id)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Prefix returns the ids whose values have the given prefix.
|
||||
func (idx *ListIndex) Prefix(prefix []byte, opts *Options) ([][]byte, error) {
|
||||
var list [][]byte
|
||||
|
||||
c := internal.PrefixCursor{
|
||||
C: idx.IndexBucket.Cursor(),
|
||||
Reverse: opts != nil && opts.Reverse,
|
||||
Prefix: prefix,
|
||||
}
|
||||
|
||||
for k, id := c.First(); k != nil && c.Continue(k); k, id = c.Next() {
|
||||
if id == nil || bytes.Equal(k, []byte("storm__ids")) {
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Skip > 0 {
|
||||
opts.Skip--
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit > 0 {
|
||||
opts.Limit--
|
||||
}
|
||||
|
||||
list = append(list, id)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func generatePrefix(value []byte) []byte {
|
||||
prefix := make([]byte, len(value)+2)
|
||||
var i int
|
||||
for i = range value {
|
||||
prefix[i] = value[i]
|
||||
}
|
||||
prefix[i+1] = '_'
|
||||
prefix[i+2] = '_'
|
||||
return prefix
|
||||
}
|
15
vendor/github.com/asdine/storm/v3/index/options.go
generated
vendored
Normal file
15
vendor/github.com/asdine/storm/v3/index/options.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
package index
|
||||
|
||||
// NewOptions creates initialized Options
|
||||
func NewOptions() *Options {
|
||||
return &Options{
|
||||
Limit: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// Options are used to customize queries
|
||||
type Options struct {
|
||||
Limit int
|
||||
Skip int
|
||||
Reverse bool
|
||||
}
|
183
vendor/github.com/asdine/storm/v3/index/unique.go
generated
vendored
Normal file
183
vendor/github.com/asdine/storm/v3/index/unique.go
generated
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/asdine/storm/v3/internal"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// NewUniqueIndex loads a UniqueIndex
|
||||
func NewUniqueIndex(parent *bolt.Bucket, indexName []byte) (*UniqueIndex, error) {
|
||||
var err error
|
||||
b := parent.Bucket(indexName)
|
||||
if b == nil {
|
||||
if !parent.Writable() {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
b, err = parent.CreateBucket(indexName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &UniqueIndex{
|
||||
IndexBucket: b,
|
||||
Parent: parent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UniqueIndex is an index that references unique values and the corresponding ID.
|
||||
type UniqueIndex struct {
|
||||
Parent *bolt.Bucket
|
||||
IndexBucket *bolt.Bucket
|
||||
}
|
||||
|
||||
// Add a value to the unique index
|
||||
func (idx *UniqueIndex) Add(value []byte, targetID []byte) error {
|
||||
if value == nil || len(value) == 0 {
|
||||
return ErrNilParam
|
||||
}
|
||||
if targetID == nil || len(targetID) == 0 {
|
||||
return ErrNilParam
|
||||
}
|
||||
|
||||
exists := idx.IndexBucket.Get(value)
|
||||
if exists != nil {
|
||||
if bytes.Equal(exists, targetID) {
|
||||
return nil
|
||||
}
|
||||
return ErrAlreadyExists
|
||||
}
|
||||
|
||||
return idx.IndexBucket.Put(value, targetID)
|
||||
}
|
||||
|
||||
// Remove a value from the unique index
|
||||
func (idx *UniqueIndex) Remove(value []byte) error {
|
||||
return idx.IndexBucket.Delete(value)
|
||||
}
|
||||
|
||||
// RemoveID removes an ID from the unique index
|
||||
func (idx *UniqueIndex) RemoveID(id []byte) error {
|
||||
c := idx.IndexBucket.Cursor()
|
||||
|
||||
for val, ident := c.First(); val != nil; val, ident = c.Next() {
|
||||
if bytes.Equal(ident, id) {
|
||||
return idx.Remove(val)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the id corresponding to the given value
|
||||
func (idx *UniqueIndex) Get(value []byte) []byte {
|
||||
return idx.IndexBucket.Get(value)
|
||||
}
|
||||
|
||||
// All returns all the ids corresponding to the given value
|
||||
func (idx *UniqueIndex) All(value []byte, opts *Options) ([][]byte, error) {
|
||||
id := idx.IndexBucket.Get(value)
|
||||
if id != nil {
|
||||
return [][]byte{id}, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// AllRecords returns all the IDs of this index
|
||||
func (idx *UniqueIndex) AllRecords(opts *Options) ([][]byte, error) {
|
||||
var list [][]byte
|
||||
|
||||
c := internal.Cursor{C: idx.IndexBucket.Cursor(), Reverse: opts != nil && opts.Reverse}
|
||||
|
||||
for val, ident := c.First(); val != nil; val, ident = c.Next() {
|
||||
if opts != nil && opts.Skip > 0 {
|
||||
opts.Skip--
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit > 0 {
|
||||
opts.Limit--
|
||||
}
|
||||
|
||||
list = append(list, ident)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Range returns the ids corresponding to the given range of values
|
||||
func (idx *UniqueIndex) Range(min []byte, max []byte, opts *Options) ([][]byte, error) {
|
||||
var list [][]byte
|
||||
|
||||
c := internal.RangeCursor{
|
||||
C: idx.IndexBucket.Cursor(),
|
||||
Reverse: opts != nil && opts.Reverse,
|
||||
Min: min,
|
||||
Max: max,
|
||||
CompareFn: func(val, limit []byte) int {
|
||||
return bytes.Compare(val, limit)
|
||||
},
|
||||
}
|
||||
|
||||
for val, ident := c.First(); val != nil && c.Continue(val); val, ident = c.Next() {
|
||||
if opts != nil && opts.Skip > 0 {
|
||||
opts.Skip--
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit > 0 {
|
||||
opts.Limit--
|
||||
}
|
||||
|
||||
list = append(list, ident)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Prefix returns the ids whose values have the given prefix.
|
||||
func (idx *UniqueIndex) Prefix(prefix []byte, opts *Options) ([][]byte, error) {
|
||||
var list [][]byte
|
||||
|
||||
c := internal.PrefixCursor{
|
||||
C: idx.IndexBucket.Cursor(),
|
||||
Reverse: opts != nil && opts.Reverse,
|
||||
Prefix: prefix,
|
||||
}
|
||||
|
||||
for val, ident := c.First(); val != nil && c.Continue(val); val, ident = c.Next() {
|
||||
if opts != nil && opts.Skip > 0 {
|
||||
opts.Skip--
|
||||
continue
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
if opts != nil && opts.Limit > 0 {
|
||||
opts.Limit--
|
||||
}
|
||||
|
||||
list = append(list, ident)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// first returns the first ID of this index
|
||||
func (idx *UniqueIndex) first() []byte {
|
||||
c := idx.IndexBucket.Cursor()
|
||||
|
||||
for val, ident := c.First(); val != nil; val, ident = c.Next() {
|
||||
return ident
|
||||
}
|
||||
return nil
|
||||
}
|
121
vendor/github.com/asdine/storm/v3/internal/boltdb.go
generated
vendored
Normal file
121
vendor/github.com/asdine/storm/v3/internal/boltdb.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// Cursor that can be reversed
|
||||
type Cursor struct {
|
||||
C *bolt.Cursor
|
||||
Reverse bool
|
||||
}
|
||||
|
||||
// First element
|
||||
func (c *Cursor) First() ([]byte, []byte) {
|
||||
if c.Reverse {
|
||||
return c.C.Last()
|
||||
}
|
||||
|
||||
return c.C.First()
|
||||
}
|
||||
|
||||
// Next element
|
||||
func (c *Cursor) Next() ([]byte, []byte) {
|
||||
if c.Reverse {
|
||||
return c.C.Prev()
|
||||
}
|
||||
|
||||
return c.C.Next()
|
||||
}
|
||||
|
||||
// RangeCursor that can be reversed
|
||||
type RangeCursor struct {
|
||||
C *bolt.Cursor
|
||||
Reverse bool
|
||||
Min []byte
|
||||
Max []byte
|
||||
CompareFn func([]byte, []byte) int
|
||||
}
|
||||
|
||||
// First element
|
||||
func (c *RangeCursor) First() ([]byte, []byte) {
|
||||
if c.Reverse {
|
||||
k, v := c.C.Seek(c.Max)
|
||||
|
||||
// If Seek doesn't find a key it goes to the next.
|
||||
// If so, we need to get the previous one to avoid
|
||||
// including bigger values. #218
|
||||
if !bytes.HasPrefix(k, c.Max) && k != nil {
|
||||
k, v = c.C.Prev()
|
||||
}
|
||||
|
||||
return k, v
|
||||
}
|
||||
|
||||
return c.C.Seek(c.Min)
|
||||
}
|
||||
|
||||
// Next element
|
||||
func (c *RangeCursor) Next() ([]byte, []byte) {
|
||||
if c.Reverse {
|
||||
return c.C.Prev()
|
||||
}
|
||||
|
||||
return c.C.Next()
|
||||
}
|
||||
|
||||
// Continue tells if the loop needs to continue
|
||||
func (c *RangeCursor) Continue(val []byte) bool {
|
||||
if c.Reverse {
|
||||
return val != nil && c.CompareFn(val, c.Min) >= 0
|
||||
}
|
||||
|
||||
return val != nil && c.CompareFn(val, c.Max) <= 0
|
||||
}
|
||||
|
||||
// PrefixCursor that can be reversed
|
||||
type PrefixCursor struct {
|
||||
C *bolt.Cursor
|
||||
Reverse bool
|
||||
Prefix []byte
|
||||
}
|
||||
|
||||
// First element
|
||||
func (c *PrefixCursor) First() ([]byte, []byte) {
|
||||
var k, v []byte
|
||||
|
||||
for k, v = c.C.First(); k != nil && !bytes.HasPrefix(k, c.Prefix); k, v = c.C.Next() {
|
||||
}
|
||||
|
||||
if k == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if c.Reverse {
|
||||
kc, vc := k, v
|
||||
for ; kc != nil && bytes.HasPrefix(kc, c.Prefix); kc, vc = c.C.Next() {
|
||||
k, v = kc, vc
|
||||
}
|
||||
if kc != nil {
|
||||
k, v = c.C.Prev()
|
||||
}
|
||||
}
|
||||
|
||||
return k, v
|
||||
}
|
||||
|
||||
// Next element
|
||||
func (c *PrefixCursor) Next() ([]byte, []byte) {
|
||||
if c.Reverse {
|
||||
return c.C.Prev()
|
||||
}
|
||||
|
||||
return c.C.Next()
|
||||
}
|
||||
|
||||
// Continue tells if the loop needs to continue
|
||||
func (c *PrefixCursor) Continue(val []byte) bool {
|
||||
return val != nil && bytes.HasPrefix(val, c.Prefix)
|
||||
}
|
170
vendor/github.com/asdine/storm/v3/kv.go
generated
vendored
Normal file
170
vendor/github.com/asdine/storm/v3/kv.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// KeyValueStore can store and fetch values by key
|
||||
type KeyValueStore interface {
|
||||
// Get a value from a bucket
|
||||
Get(bucketName string, key interface{}, to interface{}) error
|
||||
// Set a key/value pair into a bucket
|
||||
Set(bucketName string, key interface{}, value interface{}) error
|
||||
// Delete deletes a key from a bucket
|
||||
Delete(bucketName string, key interface{}) error
|
||||
// GetBytes gets a raw value from a bucket.
|
||||
GetBytes(bucketName string, key interface{}) ([]byte, error)
|
||||
// SetBytes sets a raw value into a bucket.
|
||||
SetBytes(bucketName string, key interface{}, value []byte) error
|
||||
// KeyExists reports the presence of a key in a bucket.
|
||||
KeyExists(bucketName string, key interface{}) (bool, error)
|
||||
}
|
||||
|
||||
// GetBytes gets a raw value from a bucket.
|
||||
func (n *node) GetBytes(bucketName string, key interface{}) ([]byte, error) {
|
||||
id, err := toBytes(key, n.codec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var val []byte
|
||||
return val, n.readTx(func(tx *bolt.Tx) error {
|
||||
raw, err := n.getBytes(tx, bucketName, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = make([]byte, len(raw))
|
||||
copy(val, raw)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetBytes gets a raw value from a bucket.
|
||||
func (n *node) getBytes(tx *bolt.Tx, bucketName string, id []byte) ([]byte, error) {
|
||||
bucket := n.GetBucket(tx, bucketName)
|
||||
if bucket == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
raw := bucket.Get(id)
|
||||
if raw == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
// SetBytes sets a raw value into a bucket.
|
||||
func (n *node) SetBytes(bucketName string, key interface{}, value []byte) error {
|
||||
if key == nil {
|
||||
return ErrNilParam
|
||||
}
|
||||
|
||||
id, err := toBytes(key, n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readWriteTx(func(tx *bolt.Tx) error {
|
||||
return n.setBytes(tx, bucketName, id, value)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) setBytes(tx *bolt.Tx, bucketName string, id, data []byte) error {
|
||||
bucket, err := n.CreateBucketIfNotExists(tx, bucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save node configuration in the bucket
|
||||
_, err = newMeta(bucket, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(id, data)
|
||||
}
|
||||
|
||||
// Get a value from a bucket
|
||||
func (n *node) Get(bucketName string, key interface{}, to interface{}) error {
|
||||
ref := reflect.ValueOf(to)
|
||||
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr {
|
||||
return ErrPtrNeeded
|
||||
}
|
||||
|
||||
id, err := toBytes(key, n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readTx(func(tx *bolt.Tx) error {
|
||||
raw, err := n.getBytes(tx, bucketName, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.codec.Unmarshal(raw, to)
|
||||
})
|
||||
}
|
||||
|
||||
// Set a key/value pair into a bucket
|
||||
func (n *node) Set(bucketName string, key interface{}, value interface{}) error {
|
||||
var data []byte
|
||||
var err error
|
||||
if value != nil {
|
||||
data, err = n.codec.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return n.SetBytes(bucketName, key, data)
|
||||
}
|
||||
|
||||
// Delete deletes a key from a bucket
|
||||
func (n *node) Delete(bucketName string, key interface{}) error {
|
||||
id, err := toBytes(key, n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readWriteTx(func(tx *bolt.Tx) error {
|
||||
return n.delete(tx, bucketName, id)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) delete(tx *bolt.Tx, bucketName string, id []byte) error {
|
||||
bucket := n.GetBucket(tx, bucketName)
|
||||
if bucket == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return bucket.Delete(id)
|
||||
}
|
||||
|
||||
// KeyExists reports the presence of a key in a bucket.
|
||||
func (n *node) KeyExists(bucketName string, key interface{}) (bool, error) {
|
||||
id, err := toBytes(key, n.codec)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var exists bool
|
||||
return exists, n.readTx(func(tx *bolt.Tx) error {
|
||||
bucket := n.GetBucket(tx, bucketName)
|
||||
if bucket == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
v := bucket.Get(id)
|
||||
if v != nil {
|
||||
exists = true
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
69
vendor/github.com/asdine/storm/v3/metadata.go
generated
vendored
Normal file
69
vendor/github.com/asdine/storm/v3/metadata.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
const (
|
||||
metaCodec = "codec"
|
||||
)
|
||||
|
||||
func newMeta(b *bolt.Bucket, n Node) (*meta, error) {
|
||||
m := b.Bucket([]byte(metadataBucket))
|
||||
if m != nil {
|
||||
name := m.Get([]byte(metaCodec))
|
||||
if string(name) != n.Codec().Name() {
|
||||
return nil, ErrDifferentCodec
|
||||
}
|
||||
return &meta{
|
||||
node: n,
|
||||
bucket: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
m, err := b.CreateBucket([]byte(metadataBucket))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.Put([]byte(metaCodec), []byte(n.Codec().Name()))
|
||||
return &meta{
|
||||
node: n,
|
||||
bucket: m,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type meta struct {
|
||||
node Node
|
||||
bucket *bolt.Bucket
|
||||
}
|
||||
|
||||
func (m *meta) increment(field *fieldConfig) error {
|
||||
var err error
|
||||
counter := field.IncrementStart
|
||||
|
||||
raw := m.bucket.Get([]byte(field.Name + "counter"))
|
||||
if raw != nil {
|
||||
counter, err = numberfromb(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
counter++
|
||||
}
|
||||
|
||||
raw, err = numbertob(counter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.bucket.Put([]byte(field.Name+"counter"), raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
field.Value.Set(reflect.ValueOf(counter).Convert(field.Value.Type()))
|
||||
field.IsZero = false
|
||||
return nil
|
||||
}
|
126
vendor/github.com/asdine/storm/v3/node.go
generated
vendored
Normal file
126
vendor/github.com/asdine/storm/v3/node.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"github.com/asdine/storm/v3/codec"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// A Node in Storm represents the API to a BoltDB bucket.
|
||||
type Node interface {
|
||||
Tx
|
||||
TypeStore
|
||||
KeyValueStore
|
||||
BucketScanner
|
||||
|
||||
// From returns a new Storm node with a new bucket root below the current.
|
||||
// All DB operations on the new node will be executed relative to this bucket.
|
||||
From(addend ...string) Node
|
||||
|
||||
// Bucket returns the bucket name as a slice from the root.
|
||||
// In the normal, simple case this will be empty.
|
||||
Bucket() []string
|
||||
|
||||
// GetBucket returns the given bucket below the current node.
|
||||
GetBucket(tx *bolt.Tx, children ...string) *bolt.Bucket
|
||||
|
||||
// CreateBucketIfNotExists creates the bucket below the current node if it doesn't
|
||||
// already exist.
|
||||
CreateBucketIfNotExists(tx *bolt.Tx, bucket string) (*bolt.Bucket, error)
|
||||
|
||||
// WithTransaction returns a New Storm node that will use the given transaction.
|
||||
WithTransaction(tx *bolt.Tx) Node
|
||||
|
||||
// Begin starts a new transaction.
|
||||
Begin(writable bool) (Node, error)
|
||||
|
||||
// Codec used by this instance of Storm
|
||||
Codec() codec.MarshalUnmarshaler
|
||||
|
||||
// WithCodec returns a New Storm Node that will use the given Codec.
|
||||
WithCodec(codec codec.MarshalUnmarshaler) Node
|
||||
|
||||
// WithBatch returns a new Storm Node with the batch mode enabled.
|
||||
WithBatch(enabled bool) Node
|
||||
}
|
||||
|
||||
// A Node in Storm represents the API to a BoltDB bucket.
|
||||
type node struct {
|
||||
s *DB
|
||||
|
||||
// The root bucket. In the normal, simple case this will be empty.
|
||||
rootBucket []string
|
||||
|
||||
// Transaction object. Nil if not in transaction
|
||||
tx *bolt.Tx
|
||||
|
||||
// Codec of this node
|
||||
codec codec.MarshalUnmarshaler
|
||||
|
||||
// Enable batch mode for read-write transaction, instead of update mode
|
||||
batchMode bool
|
||||
}
|
||||
|
||||
// From returns a new Storm Node with a new bucket root below the current.
|
||||
// All DB operations on the new node will be executed relative to this bucket.
|
||||
func (n node) From(addend ...string) Node {
|
||||
n.rootBucket = append(n.rootBucket, addend...)
|
||||
return &n
|
||||
}
|
||||
|
||||
// WithTransaction returns a new Storm Node that will use the given transaction.
|
||||
func (n node) WithTransaction(tx *bolt.Tx) Node {
|
||||
n.tx = tx
|
||||
return &n
|
||||
}
|
||||
|
||||
// WithCodec returns a new Storm Node that will use the given Codec.
|
||||
func (n node) WithCodec(codec codec.MarshalUnmarshaler) Node {
|
||||
n.codec = codec
|
||||
return &n
|
||||
}
|
||||
|
||||
// WithBatch returns a new Storm Node with the batch mode enabled.
|
||||
func (n node) WithBatch(enabled bool) Node {
|
||||
n.batchMode = enabled
|
||||
return &n
|
||||
}
|
||||
|
||||
// Bucket returns the bucket name as a slice from the root.
|
||||
// In the normal, simple case this will be empty.
|
||||
func (n *node) Bucket() []string {
|
||||
return n.rootBucket
|
||||
}
|
||||
|
||||
// Codec returns the EncodeDecoder used by this instance of Storm
|
||||
func (n *node) Codec() codec.MarshalUnmarshaler {
|
||||
return n.codec
|
||||
}
|
||||
|
||||
// Detects if already in transaction or runs a read write transaction.
|
||||
// Uses batch mode if enabled.
|
||||
func (n *node) readWriteTx(fn func(tx *bolt.Tx) error) error {
|
||||
if n.tx != nil {
|
||||
return fn(n.tx)
|
||||
}
|
||||
|
||||
if n.batchMode {
|
||||
return n.s.Bolt.Batch(func(tx *bolt.Tx) error {
|
||||
return fn(tx)
|
||||
})
|
||||
}
|
||||
|
||||
return n.s.Bolt.Update(func(tx *bolt.Tx) error {
|
||||
return fn(tx)
|
||||
})
|
||||
}
|
||||
|
||||
// Detects if already in transaction or runs a read transaction.
|
||||
func (n *node) readTx(fn func(tx *bolt.Tx) error) error {
|
||||
if n.tx != nil {
|
||||
return fn(n.tx)
|
||||
}
|
||||
|
||||
return n.s.Bolt.View(func(tx *bolt.Tx) error {
|
||||
return fn(tx)
|
||||
})
|
||||
}
|
97
vendor/github.com/asdine/storm/v3/options.go
generated
vendored
Normal file
97
vendor/github.com/asdine/storm/v3/options.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/asdine/storm/v3/codec"
|
||||
"github.com/asdine/storm/v3/index"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// BoltOptions used to pass options to BoltDB.
|
||||
func BoltOptions(mode os.FileMode, options *bolt.Options) func(*Options) error {
|
||||
return func(opts *Options) error {
|
||||
opts.boltMode = mode
|
||||
opts.boltOptions = options
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Codec used to set a custom encoder and decoder. The default is JSON.
|
||||
func Codec(c codec.MarshalUnmarshaler) func(*Options) error {
|
||||
return func(opts *Options) error {
|
||||
opts.codec = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Batch enables the use of batch instead of update for read-write transactions.
|
||||
func Batch() func(*Options) error {
|
||||
return func(opts *Options) error {
|
||||
opts.batchMode = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Root used to set the root bucket. See also the From method.
|
||||
func Root(root ...string) func(*Options) error {
|
||||
return func(opts *Options) error {
|
||||
opts.rootBucket = root
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// UseDB allows Storm to use an existing open Bolt.DB.
|
||||
// Warning: storm.DB.Close() will close the bolt.DB instance.
|
||||
func UseDB(b *bolt.DB) func(*Options) error {
|
||||
return func(opts *Options) error {
|
||||
opts.path = b.Path()
|
||||
opts.bolt = b
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Limit sets the maximum number of records to return
|
||||
func Limit(limit int) func(*index.Options) {
|
||||
return func(opts *index.Options) {
|
||||
opts.Limit = limit
|
||||
}
|
||||
}
|
||||
|
||||
// Skip sets the number of records to skip
|
||||
func Skip(offset int) func(*index.Options) {
|
||||
return func(opts *index.Options) {
|
||||
opts.Skip = offset
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse will return the results in descending order
|
||||
func Reverse() func(*index.Options) {
|
||||
return func(opts *index.Options) {
|
||||
opts.Reverse = true
|
||||
}
|
||||
}
|
||||
|
||||
// Options are used to customize the way Storm opens a database.
|
||||
type Options struct {
|
||||
// Handles encoding and decoding of objects
|
||||
codec codec.MarshalUnmarshaler
|
||||
|
||||
// Bolt file mode
|
||||
boltMode os.FileMode
|
||||
|
||||
// Bolt options
|
||||
boltOptions *bolt.Options
|
||||
|
||||
// Enable batch mode for read-write transaction, instead of update mode
|
||||
batchMode bool
|
||||
|
||||
// The root bucket name
|
||||
rootBucket []string
|
||||
|
||||
// Path of the database file
|
||||
path string
|
||||
|
||||
// Bolt is still easily accessible
|
||||
bolt *bolt.DB
|
||||
}
|
122
vendor/github.com/asdine/storm/v3/q/compare.go
generated
vendored
Normal file
122
vendor/github.com/asdine/storm/v3/q/compare.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
package q
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func compare(a, b interface{}, tok token.Token) bool {
|
||||
vala := reflect.ValueOf(a)
|
||||
valb := reflect.ValueOf(b)
|
||||
|
||||
ak := vala.Kind()
|
||||
bk := valb.Kind()
|
||||
switch {
|
||||
// comparing nil values
|
||||
case (ak == reflect.Ptr || ak == reflect.Slice || ak == reflect.Interface || ak == reflect.Invalid) &&
|
||||
(bk == reflect.Ptr || ak == reflect.Slice || bk == reflect.Interface || bk == reflect.Invalid) &&
|
||||
(!vala.IsValid() || vala.IsNil()) && (!valb.IsValid() || valb.IsNil()):
|
||||
return true
|
||||
case ak >= reflect.Int && ak <= reflect.Int64:
|
||||
if bk >= reflect.Int && bk <= reflect.Int64 {
|
||||
return constant.Compare(constant.MakeInt64(vala.Int()), tok, constant.MakeInt64(valb.Int()))
|
||||
}
|
||||
|
||||
if bk >= reflect.Uint && bk <= reflect.Uint64 {
|
||||
return constant.Compare(constant.MakeInt64(vala.Int()), tok, constant.MakeInt64(int64(valb.Uint())))
|
||||
}
|
||||
|
||||
if bk == reflect.Float32 || bk == reflect.Float64 {
|
||||
return constant.Compare(constant.MakeFloat64(float64(vala.Int())), tok, constant.MakeFloat64(valb.Float()))
|
||||
}
|
||||
|
||||
if bk == reflect.String {
|
||||
bla, err := strconv.ParseFloat(valb.String(), 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return constant.Compare(constant.MakeFloat64(float64(vala.Int())), tok, constant.MakeFloat64(bla))
|
||||
}
|
||||
case ak >= reflect.Uint && ak <= reflect.Uint64:
|
||||
if bk >= reflect.Uint && bk <= reflect.Uint64 {
|
||||
return constant.Compare(constant.MakeUint64(vala.Uint()), tok, constant.MakeUint64(valb.Uint()))
|
||||
}
|
||||
|
||||
if bk >= reflect.Int && bk <= reflect.Int64 {
|
||||
return constant.Compare(constant.MakeUint64(vala.Uint()), tok, constant.MakeUint64(uint64(valb.Int())))
|
||||
}
|
||||
|
||||
if bk == reflect.Float32 || bk == reflect.Float64 {
|
||||
return constant.Compare(constant.MakeFloat64(float64(vala.Uint())), tok, constant.MakeFloat64(valb.Float()))
|
||||
}
|
||||
|
||||
if bk == reflect.String {
|
||||
bla, err := strconv.ParseFloat(valb.String(), 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return constant.Compare(constant.MakeFloat64(float64(vala.Uint())), tok, constant.MakeFloat64(bla))
|
||||
}
|
||||
case ak == reflect.Float32 || ak == reflect.Float64:
|
||||
if bk == reflect.Float32 || bk == reflect.Float64 {
|
||||
return constant.Compare(constant.MakeFloat64(vala.Float()), tok, constant.MakeFloat64(valb.Float()))
|
||||
}
|
||||
|
||||
if bk >= reflect.Int && bk <= reflect.Int64 {
|
||||
return constant.Compare(constant.MakeFloat64(vala.Float()), tok, constant.MakeFloat64(float64(valb.Int())))
|
||||
}
|
||||
|
||||
if bk >= reflect.Uint && bk <= reflect.Uint64 {
|
||||
return constant.Compare(constant.MakeFloat64(vala.Float()), tok, constant.MakeFloat64(float64(valb.Uint())))
|
||||
}
|
||||
|
||||
if bk == reflect.String {
|
||||
bla, err := strconv.ParseFloat(valb.String(), 64)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return constant.Compare(constant.MakeFloat64(vala.Float()), tok, constant.MakeFloat64(bla))
|
||||
}
|
||||
case ak == reflect.String:
|
||||
if bk == reflect.String {
|
||||
return constant.Compare(constant.MakeString(vala.String()), tok, constant.MakeString(valb.String()))
|
||||
}
|
||||
}
|
||||
|
||||
typea, typeb := reflect.TypeOf(a), reflect.TypeOf(b)
|
||||
|
||||
if typea != nil && (typea.String() == "time.Time" || typea.String() == "*time.Time") &&
|
||||
typeb != nil && (typeb.String() == "time.Time" || typeb.String() == "*time.Time") {
|
||||
|
||||
if typea.String() == "*time.Time" && vala.IsNil() {
|
||||
return true
|
||||
}
|
||||
|
||||
if typeb.String() == "*time.Time" {
|
||||
if valb.IsNil() {
|
||||
return true
|
||||
}
|
||||
valb = valb.Elem()
|
||||
}
|
||||
|
||||
var x, y int64
|
||||
x = 1
|
||||
if vala.MethodByName("Equal").Call([]reflect.Value{valb})[0].Bool() {
|
||||
y = 1
|
||||
} else if vala.MethodByName("Before").Call([]reflect.Value{valb})[0].Bool() {
|
||||
y = 2
|
||||
}
|
||||
return constant.Compare(constant.MakeInt64(x), tok, constant.MakeInt64(y))
|
||||
}
|
||||
|
||||
if tok == token.EQL {
|
||||
return reflect.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
67
vendor/github.com/asdine/storm/v3/q/fieldmatcher.go
generated
vendored
Normal file
67
vendor/github.com/asdine/storm/v3/q/fieldmatcher.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package q
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/token"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ErrUnknownField is returned when an unknown field is passed.
|
||||
var ErrUnknownField = errors.New("unknown field")
|
||||
|
||||
type fieldMatcherDelegate struct {
|
||||
FieldMatcher
|
||||
Field string
|
||||
}
|
||||
|
||||
// NewFieldMatcher creates a Matcher for a given field.
|
||||
func NewFieldMatcher(field string, fm FieldMatcher) Matcher {
|
||||
return fieldMatcherDelegate{Field: field, FieldMatcher: fm}
|
||||
}
|
||||
|
||||
// FieldMatcher can be used in NewFieldMatcher as a simple way to create the
|
||||
// most common Matcher: A Matcher that evaluates one field's value.
|
||||
// For more complex scenarios, implement the Matcher interface directly.
|
||||
type FieldMatcher interface {
|
||||
MatchField(v interface{}) (bool, error)
|
||||
}
|
||||
|
||||
func (r fieldMatcherDelegate) Match(i interface{}) (bool, error) {
|
||||
v := reflect.Indirect(reflect.ValueOf(i))
|
||||
return r.MatchValue(&v)
|
||||
}
|
||||
|
||||
func (r fieldMatcherDelegate) MatchValue(v *reflect.Value) (bool, error) {
|
||||
field := v.FieldByName(r.Field)
|
||||
if !field.IsValid() {
|
||||
return false, ErrUnknownField
|
||||
}
|
||||
return r.MatchField(field.Interface())
|
||||
}
|
||||
|
||||
// NewField2FieldMatcher creates a Matcher for a given field1 and field2.
|
||||
func NewField2FieldMatcher(field1, field2 string, tok token.Token) Matcher {
|
||||
return field2fieldMatcherDelegate{Field1: field1, Field2: field2, Tok: tok}
|
||||
}
|
||||
|
||||
type field2fieldMatcherDelegate struct {
|
||||
Field1, Field2 string
|
||||
Tok token.Token
|
||||
}
|
||||
|
||||
func (r field2fieldMatcherDelegate) Match(i interface{}) (bool, error) {
|
||||
v := reflect.Indirect(reflect.ValueOf(i))
|
||||
return r.MatchValue(&v)
|
||||
}
|
||||
|
||||
func (r field2fieldMatcherDelegate) MatchValue(v *reflect.Value) (bool, error) {
|
||||
field1 := v.FieldByName(r.Field1)
|
||||
if !field1.IsValid() {
|
||||
return false, ErrUnknownField
|
||||
}
|
||||
field2 := v.FieldByName(r.Field2)
|
||||
if !field2.IsValid() {
|
||||
return false, ErrUnknownField
|
||||
}
|
||||
return compare(field1.Interface(), field2.Interface(), r.Tok), nil
|
||||
}
|
51
vendor/github.com/asdine/storm/v3/q/regexp.go
generated
vendored
Normal file
51
vendor/github.com/asdine/storm/v3/q/regexp.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package q
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Re creates a regexp matcher. It checks if the given field matches the given regexp.
|
||||
// Note that this only supports fields of type string or []byte.
|
||||
func Re(field string, re string) Matcher {
|
||||
regexpCache.RLock()
|
||||
if r, ok := regexpCache.m[re]; ok {
|
||||
regexpCache.RUnlock()
|
||||
return NewFieldMatcher(field, ®expMatcher{r: r})
|
||||
}
|
||||
regexpCache.RUnlock()
|
||||
|
||||
regexpCache.Lock()
|
||||
r, err := regexp.Compile(re)
|
||||
if err == nil {
|
||||
regexpCache.m[re] = r
|
||||
}
|
||||
regexpCache.Unlock()
|
||||
|
||||
return NewFieldMatcher(field, ®expMatcher{r: r, err: err})
|
||||
}
|
||||
|
||||
var regexpCache = struct {
|
||||
sync.RWMutex
|
||||
m map[string]*regexp.Regexp
|
||||
}{m: make(map[string]*regexp.Regexp)}
|
||||
|
||||
type regexpMatcher struct {
|
||||
r *regexp.Regexp
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *regexpMatcher) MatchField(v interface{}) (bool, error) {
|
||||
if r.err != nil {
|
||||
return false, r.err
|
||||
}
|
||||
switch fieldValue := v.(type) {
|
||||
case string:
|
||||
return r.r.MatchString(fieldValue), nil
|
||||
case []byte:
|
||||
return r.r.Match(fieldValue), nil
|
||||
default:
|
||||
return false, fmt.Errorf("Only string and []byte supported for regexp matcher, got %T", fieldValue)
|
||||
}
|
||||
}
|
247
vendor/github.com/asdine/storm/v3/q/tree.go
generated
vendored
Normal file
247
vendor/github.com/asdine/storm/v3/q/tree.go
generated
vendored
Normal file
@ -0,0 +1,247 @@
|
||||
// Package q contains a list of Matchers used to compare struct fields with values
|
||||
package q
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// A Matcher is used to test against a record to see if it matches.
|
||||
type Matcher interface {
|
||||
// Match is used to test the criteria against a structure.
|
||||
Match(interface{}) (bool, error)
|
||||
}
|
||||
|
||||
// A ValueMatcher is used to test against a reflect.Value.
|
||||
type ValueMatcher interface {
|
||||
// MatchValue tests if the given reflect.Value matches.
|
||||
// It is useful when the reflect.Value of an object already exists.
|
||||
MatchValue(*reflect.Value) (bool, error)
|
||||
}
|
||||
|
||||
type cmp struct {
|
||||
value interface{}
|
||||
token token.Token
|
||||
}
|
||||
|
||||
func (c *cmp) MatchField(v interface{}) (bool, error) {
|
||||
return compare(v, c.value, c.token), nil
|
||||
}
|
||||
|
||||
type trueMatcher struct{}
|
||||
|
||||
func (*trueMatcher) Match(i interface{}) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (*trueMatcher) MatchValue(v *reflect.Value) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type or struct {
|
||||
children []Matcher
|
||||
}
|
||||
|
||||
func (c *or) Match(i interface{}) (bool, error) {
|
||||
v := reflect.Indirect(reflect.ValueOf(i))
|
||||
return c.MatchValue(&v)
|
||||
}
|
||||
|
||||
func (c *or) MatchValue(v *reflect.Value) (bool, error) {
|
||||
for _, matcher := range c.children {
|
||||
if vm, ok := matcher.(ValueMatcher); ok {
|
||||
ok, err := vm.MatchValue(v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ok, err := matcher.Match(v.Interface())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type and struct {
|
||||
children []Matcher
|
||||
}
|
||||
|
||||
func (c *and) Match(i interface{}) (bool, error) {
|
||||
v := reflect.Indirect(reflect.ValueOf(i))
|
||||
return c.MatchValue(&v)
|
||||
}
|
||||
|
||||
func (c *and) MatchValue(v *reflect.Value) (bool, error) {
|
||||
for _, matcher := range c.children {
|
||||
if vm, ok := matcher.(ValueMatcher); ok {
|
||||
ok, err := vm.MatchValue(v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ok, err := matcher.Match(v.Interface())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
type strictEq struct {
|
||||
field string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (s *strictEq) MatchField(v interface{}) (bool, error) {
|
||||
return reflect.DeepEqual(v, s.value), nil
|
||||
}
|
||||
|
||||
type in struct {
|
||||
list interface{}
|
||||
}
|
||||
|
||||
func (i *in) MatchField(v interface{}) (bool, error) {
|
||||
ref := reflect.ValueOf(i.list)
|
||||
if ref.Kind() != reflect.Slice {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
c := cmp{
|
||||
token: token.EQL,
|
||||
}
|
||||
|
||||
for i := 0; i < ref.Len(); i++ {
|
||||
c.value = ref.Index(i).Interface()
|
||||
ok, err := c.MatchField(v)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type not struct {
|
||||
children []Matcher
|
||||
}
|
||||
|
||||
func (n *not) Match(i interface{}) (bool, error) {
|
||||
v := reflect.Indirect(reflect.ValueOf(i))
|
||||
return n.MatchValue(&v)
|
||||
}
|
||||
|
||||
func (n *not) MatchValue(v *reflect.Value) (bool, error) {
|
||||
var err error
|
||||
|
||||
for _, matcher := range n.children {
|
||||
vm, ok := matcher.(ValueMatcher)
|
||||
if ok {
|
||||
ok, err = vm.MatchValue(v)
|
||||
} else {
|
||||
ok, err = matcher.Match(v.Interface())
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Eq matcher, checks if the given field is equal to the given value
|
||||
func Eq(field string, v interface{}) Matcher {
|
||||
return NewFieldMatcher(field, &cmp{value: v, token: token.EQL})
|
||||
}
|
||||
|
||||
// EqF matcher, checks if the given field is equal to the given field
|
||||
func EqF(field1, field2 string) Matcher {
|
||||
return NewField2FieldMatcher(field1, field2, token.EQL)
|
||||
}
|
||||
|
||||
// StrictEq matcher, checks if the given field is deeply equal to the given value
|
||||
func StrictEq(field string, v interface{}) Matcher {
|
||||
return NewFieldMatcher(field, &strictEq{value: v})
|
||||
}
|
||||
|
||||
// Gt matcher, checks if the given field is greater than the given value
|
||||
func Gt(field string, v interface{}) Matcher {
|
||||
return NewFieldMatcher(field, &cmp{value: v, token: token.GTR})
|
||||
}
|
||||
|
||||
// GtF matcher, checks if the given field is greater than the given field
|
||||
func GtF(field1, field2 string) Matcher {
|
||||
return NewField2FieldMatcher(field1, field2, token.GTR)
|
||||
}
|
||||
|
||||
// Gte matcher, checks if the given field is greater than or equal to the given value
|
||||
func Gte(field string, v interface{}) Matcher {
|
||||
return NewFieldMatcher(field, &cmp{value: v, token: token.GEQ})
|
||||
}
|
||||
|
||||
// GteF matcher, checks if the given field is greater than or equal to the given field
|
||||
func GteF(field1, field2 string) Matcher {
|
||||
return NewField2FieldMatcher(field1, field2, token.GEQ)
|
||||
}
|
||||
|
||||
// Lt matcher, checks if the given field is lesser than the given value
|
||||
func Lt(field string, v interface{}) Matcher {
|
||||
return NewFieldMatcher(field, &cmp{value: v, token: token.LSS})
|
||||
}
|
||||
|
||||
// LtF matcher, checks if the given field is lesser than the given field
|
||||
func LtF(field1, field2 string) Matcher {
|
||||
return NewField2FieldMatcher(field1, field2, token.LSS)
|
||||
}
|
||||
|
||||
// Lte matcher, checks if the given field is lesser than or equal to the given value
|
||||
func Lte(field string, v interface{}) Matcher {
|
||||
return NewFieldMatcher(field, &cmp{value: v, token: token.LEQ})
|
||||
}
|
||||
|
||||
// LteF matcher, checks if the given field is lesser than or equal to the given field
|
||||
func LteF(field1, field2 string) Matcher {
|
||||
return NewField2FieldMatcher(field1, field2, token.LEQ)
|
||||
}
|
||||
|
||||
// In matcher, checks if the given field matches one of the value of the given slice.
|
||||
// v must be a slice.
|
||||
func In(field string, v interface{}) Matcher {
|
||||
return NewFieldMatcher(field, &in{list: v})
|
||||
}
|
||||
|
||||
// True matcher, always returns true
|
||||
func True() Matcher { return &trueMatcher{} }
|
||||
|
||||
// Or matcher, checks if at least one of the given matchers matches the record
|
||||
func Or(matchers ...Matcher) Matcher { return &or{children: matchers} }
|
||||
|
||||
// And matcher, checks if all of the given matchers matches the record
|
||||
func And(matchers ...Matcher) Matcher { return &and{children: matchers} }
|
||||
|
||||
// Not matcher, checks if all of the given matchers return false
|
||||
func Not(matchers ...Matcher) Matcher { return ¬{children: matchers} }
|
219
vendor/github.com/asdine/storm/v3/query.go
generated
vendored
Normal file
219
vendor/github.com/asdine/storm/v3/query.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"github.com/asdine/storm/v3/internal"
|
||||
"github.com/asdine/storm/v3/q"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// Select a list of records that match a list of matchers. Doesn't use indexes.
|
||||
func (n *node) Select(matchers ...q.Matcher) Query {
|
||||
tree := q.And(matchers...)
|
||||
return newQuery(n, tree)
|
||||
}
|
||||
|
||||
// Query is the low level query engine used by Storm. It allows to operate searches through an entire bucket.
|
||||
type Query interface {
|
||||
// Skip matching records by the given number
|
||||
Skip(int) Query
|
||||
|
||||
// Limit the results by the given number
|
||||
Limit(int) Query
|
||||
|
||||
// Order by the given fields, in descending precedence, left-to-right.
|
||||
OrderBy(...string) Query
|
||||
|
||||
// Reverse the order of the results
|
||||
Reverse() Query
|
||||
|
||||
// Bucket specifies the bucket name
|
||||
Bucket(string) Query
|
||||
|
||||
// Find a list of matching records
|
||||
Find(interface{}) error
|
||||
|
||||
// First gets the first matching record
|
||||
First(interface{}) error
|
||||
|
||||
// Delete all matching records
|
||||
Delete(interface{}) error
|
||||
|
||||
// Count all the matching records
|
||||
Count(interface{}) (int, error)
|
||||
|
||||
// Returns all the records without decoding them
|
||||
Raw() ([][]byte, error)
|
||||
|
||||
// Execute the given function for each raw element
|
||||
RawEach(func([]byte, []byte) error) error
|
||||
|
||||
// Execute the given function for each element
|
||||
Each(interface{}, func(interface{}) error) error
|
||||
}
|
||||
|
||||
func newQuery(n *node, tree q.Matcher) *query {
|
||||
return &query{
|
||||
skip: 0,
|
||||
limit: -1,
|
||||
node: n,
|
||||
tree: tree,
|
||||
}
|
||||
}
|
||||
|
||||
type query struct {
|
||||
limit int
|
||||
skip int
|
||||
reverse bool
|
||||
tree q.Matcher
|
||||
node *node
|
||||
bucket string
|
||||
orderBy []string
|
||||
}
|
||||
|
||||
func (q *query) Skip(nb int) Query {
|
||||
q.skip = nb
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) Limit(nb int) Query {
|
||||
q.limit = nb
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) OrderBy(field ...string) Query {
|
||||
q.orderBy = field
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) Reverse() Query {
|
||||
q.reverse = true
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) Bucket(bucketName string) Query {
|
||||
q.bucket = bucketName
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *query) Find(to interface{}) error {
|
||||
sink, err := newListSink(q.node, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return q.runQuery(sink)
|
||||
}
|
||||
|
||||
func (q *query) First(to interface{}) error {
|
||||
sink, err := newFirstSink(q.node, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.limit = 1
|
||||
return q.runQuery(sink)
|
||||
}
|
||||
|
||||
func (q *query) Delete(kind interface{}) error {
|
||||
sink, err := newDeleteSink(q.node, kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return q.runQuery(sink)
|
||||
}
|
||||
|
||||
func (q *query) Count(kind interface{}) (int, error) {
|
||||
sink, err := newCountSink(q.node, kind)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = q.runQuery(sink)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return sink.counter, nil
|
||||
}
|
||||
|
||||
func (q *query) Raw() ([][]byte, error) {
|
||||
sink := newRawSink()
|
||||
|
||||
err := q.runQuery(sink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sink.results, nil
|
||||
}
|
||||
|
||||
func (q *query) RawEach(fn func([]byte, []byte) error) error {
|
||||
sink := newRawSink()
|
||||
|
||||
sink.execFn = fn
|
||||
|
||||
return q.runQuery(sink)
|
||||
}
|
||||
|
||||
func (q *query) Each(kind interface{}, fn func(interface{}) error) error {
|
||||
sink, err := newEachSink(kind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sink.execFn = fn
|
||||
|
||||
return q.runQuery(sink)
|
||||
}
|
||||
|
||||
func (q *query) runQuery(sink sink) error {
|
||||
if q.node.tx != nil {
|
||||
return q.query(q.node.tx, sink)
|
||||
}
|
||||
if sink.readOnly() {
|
||||
return q.node.s.Bolt.View(func(tx *bolt.Tx) error {
|
||||
return q.query(tx, sink)
|
||||
})
|
||||
}
|
||||
return q.node.s.Bolt.Update(func(tx *bolt.Tx) error {
|
||||
return q.query(tx, sink)
|
||||
})
|
||||
}
|
||||
|
||||
func (q *query) query(tx *bolt.Tx, sink sink) error {
|
||||
bucketName := q.bucket
|
||||
if bucketName == "" {
|
||||
bucketName = sink.bucketName()
|
||||
}
|
||||
bucket := q.node.GetBucket(tx, bucketName)
|
||||
|
||||
if q.limit == 0 {
|
||||
return sink.flush()
|
||||
}
|
||||
|
||||
sorter := newSorter(q.node, sink)
|
||||
sorter.orderBy = q.orderBy
|
||||
sorter.reverse = q.reverse
|
||||
sorter.skip = q.skip
|
||||
sorter.limit = q.limit
|
||||
if bucket != nil {
|
||||
c := internal.Cursor{C: bucket.Cursor(), Reverse: q.reverse}
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
stop, err := sorter.filter(q.tree, bucket, k, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sorter.flush()
|
||||
}
|
105
vendor/github.com/asdine/storm/v3/scan.go
generated
vendored
Normal file
105
vendor/github.com/asdine/storm/v3/scan.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// A BucketScanner scans a Node for a list of buckets
|
||||
type BucketScanner interface {
|
||||
// PrefixScan scans the root buckets for keys matching the given prefix.
|
||||
PrefixScan(prefix string) []Node
|
||||
// PrefixScan scans the buckets in this node for keys matching the given prefix.
|
||||
RangeScan(min, max string) []Node
|
||||
}
|
||||
|
||||
// PrefixScan scans the buckets in this node for keys matching the given prefix.
|
||||
func (n *node) PrefixScan(prefix string) []Node {
|
||||
if n.tx != nil {
|
||||
return n.prefixScan(n.tx, prefix)
|
||||
}
|
||||
|
||||
var nodes []Node
|
||||
|
||||
n.readTx(func(tx *bolt.Tx) error {
|
||||
nodes = n.prefixScan(tx, prefix)
|
||||
return nil
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (n *node) prefixScan(tx *bolt.Tx, prefix string) []Node {
|
||||
var (
|
||||
prefixBytes = []byte(prefix)
|
||||
nodes []Node
|
||||
c = n.cursor(tx)
|
||||
)
|
||||
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for k, v := c.Seek(prefixBytes); k != nil && bytes.HasPrefix(k, prefixBytes); k, v = c.Next() {
|
||||
if v != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
nodes = append(nodes, n.From(string(k)))
|
||||
}
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
// RangeScan scans the buckets in this node over a range such as a sortable time range.
|
||||
func (n *node) RangeScan(min, max string) []Node {
|
||||
if n.tx != nil {
|
||||
return n.rangeScan(n.tx, min, max)
|
||||
}
|
||||
|
||||
var nodes []Node
|
||||
|
||||
n.readTx(func(tx *bolt.Tx) error {
|
||||
nodes = n.rangeScan(tx, min, max)
|
||||
return nil
|
||||
})
|
||||
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (n *node) rangeScan(tx *bolt.Tx, min, max string) []Node {
|
||||
var (
|
||||
minBytes = []byte(min)
|
||||
maxBytes = []byte(max)
|
||||
nodes []Node
|
||||
c = n.cursor(tx)
|
||||
)
|
||||
|
||||
for k, v := c.Seek(minBytes); k != nil && bytes.Compare(k, maxBytes) <= 0; k, v = c.Next() {
|
||||
if v != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
nodes = append(nodes, n.From(string(k)))
|
||||
}
|
||||
|
||||
return nodes
|
||||
|
||||
}
|
||||
|
||||
func (n *node) cursor(tx *bolt.Tx) *bolt.Cursor {
|
||||
var c *bolt.Cursor
|
||||
|
||||
if len(n.rootBucket) > 0 {
|
||||
b := n.GetBucket(tx)
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
c = b.Cursor()
|
||||
} else {
|
||||
c = tx.Cursor()
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
620
vendor/github.com/asdine/storm/v3/sink.go
generated
vendored
Normal file
620
vendor/github.com/asdine/storm/v3/sink.go
generated
vendored
Normal file
@ -0,0 +1,620 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
"github.com/asdine/storm/v3/index"
|
||||
"github.com/asdine/storm/v3/q"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
type item struct {
|
||||
value *reflect.Value
|
||||
bucket *bolt.Bucket
|
||||
k []byte
|
||||
v []byte
|
||||
}
|
||||
|
||||
func newSorter(n Node, snk sink) *sorter {
|
||||
return &sorter{
|
||||
node: n,
|
||||
sink: snk,
|
||||
skip: 0,
|
||||
limit: -1,
|
||||
list: make([]*item, 0),
|
||||
err: make(chan error),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type sorter struct {
|
||||
node Node
|
||||
sink sink
|
||||
list []*item
|
||||
skip int
|
||||
limit int
|
||||
orderBy []string
|
||||
reverse bool
|
||||
err chan error
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
func (s *sorter) filter(tree q.Matcher, bucket *bolt.Bucket, k, v []byte) (bool, error) {
|
||||
itm := &item{
|
||||
bucket: bucket,
|
||||
k: k,
|
||||
v: v,
|
||||
}
|
||||
rsink, ok := s.sink.(reflectSink)
|
||||
if !ok {
|
||||
return s.add(itm)
|
||||
}
|
||||
|
||||
newElem := rsink.elem()
|
||||
if err := s.node.Codec().Unmarshal(v, newElem.Interface()); err != nil {
|
||||
return false, err
|
||||
}
|
||||
itm.value = &newElem
|
||||
|
||||
if tree != nil {
|
||||
ok, err := tree.Match(newElem.Interface())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.orderBy) == 0 {
|
||||
return s.add(itm)
|
||||
}
|
||||
|
||||
if _, ok := s.sink.(sliceSink); ok {
|
||||
// add directly to sink, we'll apply skip/limits after sorting
|
||||
return false, s.sink.add(itm)
|
||||
}
|
||||
|
||||
s.list = append(s.list, itm)
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *sorter) add(itm *item) (stop bool, err error) {
|
||||
if s.limit == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if s.skip > 0 {
|
||||
s.skip--
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if s.limit > 0 {
|
||||
s.limit--
|
||||
}
|
||||
|
||||
err = s.sink.add(itm)
|
||||
|
||||
return s.limit == 0, err
|
||||
}
|
||||
|
||||
func (s *sorter) compareValue(left reflect.Value, right reflect.Value) int {
|
||||
if !left.IsValid() || !right.IsValid() {
|
||||
if left.IsValid() {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
switch left.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
l, r := left.Int(), right.Int()
|
||||
if l < r {
|
||||
return -1
|
||||
}
|
||||
if l > r {
|
||||
return 1
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
l, r := left.Uint(), right.Uint()
|
||||
if l < r {
|
||||
return -1
|
||||
}
|
||||
if l > r {
|
||||
return 1
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
l, r := left.Float(), right.Float()
|
||||
if l < r {
|
||||
return -1
|
||||
}
|
||||
if l > r {
|
||||
return 1
|
||||
}
|
||||
case reflect.String:
|
||||
l, r := left.String(), right.String()
|
||||
if l < r {
|
||||
return -1
|
||||
}
|
||||
if l > r {
|
||||
return 1
|
||||
}
|
||||
case reflect.Struct:
|
||||
if lt, lok := left.Interface().(time.Time); lok {
|
||||
if rt, rok := right.Interface().(time.Time); rok {
|
||||
if lok && rok {
|
||||
if lt.Before(rt) {
|
||||
return -1
|
||||
} else {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
rawLeft, err := toBytes(left.Interface(), s.node.Codec())
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
rawRight, err := toBytes(right.Interface(), s.node.Codec())
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
l, r := string(rawLeft), string(rawRight)
|
||||
if l < r {
|
||||
return -1
|
||||
}
|
||||
if l > r {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *sorter) less(leftElem reflect.Value, rightElem reflect.Value) bool {
|
||||
for _, orderBy := range s.orderBy {
|
||||
leftField := reflect.Indirect(leftElem).FieldByName(orderBy)
|
||||
if !leftField.IsValid() {
|
||||
s.err <- ErrNotFound
|
||||
return false
|
||||
}
|
||||
rightField := reflect.Indirect(rightElem).FieldByName(orderBy)
|
||||
if !rightField.IsValid() {
|
||||
s.err <- ErrNotFound
|
||||
return false
|
||||
}
|
||||
|
||||
direction := 1
|
||||
if s.reverse {
|
||||
direction = -1
|
||||
}
|
||||
|
||||
switch s.compareValue(leftField, rightField) * direction {
|
||||
case -1:
|
||||
return true
|
||||
case 1:
|
||||
return false
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *sorter) flush() error {
|
||||
if len(s.orderBy) == 0 {
|
||||
return s.sink.flush()
|
||||
}
|
||||
|
||||
go func() {
|
||||
sort.Sort(s)
|
||||
close(s.err)
|
||||
}()
|
||||
err := <-s.err
|
||||
close(s.done)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ssink, ok := s.sink.(sliceSink); ok {
|
||||
if !ssink.slice().IsValid() {
|
||||
return s.sink.flush()
|
||||
}
|
||||
if s.skip >= ssink.slice().Len() {
|
||||
ssink.reset()
|
||||
return s.sink.flush()
|
||||
}
|
||||
leftBound := s.skip
|
||||
if leftBound < 0 {
|
||||
leftBound = 0
|
||||
}
|
||||
limit := s.limit
|
||||
if s.limit < 0 {
|
||||
limit = 0
|
||||
}
|
||||
|
||||
rightBound := leftBound + limit
|
||||
if rightBound > ssink.slice().Len() || rightBound == leftBound {
|
||||
rightBound = ssink.slice().Len()
|
||||
}
|
||||
ssink.setSlice(ssink.slice().Slice(leftBound, rightBound))
|
||||
return s.sink.flush()
|
||||
}
|
||||
|
||||
for _, itm := range s.list {
|
||||
if itm == nil {
|
||||
break
|
||||
}
|
||||
stop, err := s.add(itm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return s.sink.flush()
|
||||
}
|
||||
|
||||
func (s *sorter) Len() int {
|
||||
// skip if we encountered an earlier error
|
||||
select {
|
||||
case <-s.done:
|
||||
return 0
|
||||
default:
|
||||
}
|
||||
if ssink, ok := s.sink.(sliceSink); ok {
|
||||
return ssink.slice().Len()
|
||||
}
|
||||
return len(s.list)
|
||||
|
||||
}
|
||||
|
||||
func (s *sorter) Less(i, j int) bool {
|
||||
// skip if we encountered an earlier error
|
||||
select {
|
||||
case <-s.done:
|
||||
return false
|
||||
default:
|
||||
}
|
||||
|
||||
if ssink, ok := s.sink.(sliceSink); ok {
|
||||
return s.less(ssink.slice().Index(i), ssink.slice().Index(j))
|
||||
}
|
||||
return s.less(*s.list[i].value, *s.list[j].value)
|
||||
}
|
||||
|
||||
type sink interface {
|
||||
bucketName() string
|
||||
flush() error
|
||||
add(*item) error
|
||||
readOnly() bool
|
||||
}
|
||||
|
||||
type reflectSink interface {
|
||||
elem() reflect.Value
|
||||
}
|
||||
|
||||
type sliceSink interface {
|
||||
slice() reflect.Value
|
||||
setSlice(reflect.Value)
|
||||
reset()
|
||||
}
|
||||
|
||||
func newListSink(node Node, to interface{}) (*listSink, error) {
|
||||
ref := reflect.ValueOf(to)
|
||||
|
||||
if ref.Kind() != reflect.Ptr || reflect.Indirect(ref).Kind() != reflect.Slice {
|
||||
return nil, ErrSlicePtrNeeded
|
||||
}
|
||||
|
||||
sliceType := reflect.Indirect(ref).Type()
|
||||
elemType := sliceType.Elem()
|
||||
|
||||
if elemType.Kind() == reflect.Ptr {
|
||||
elemType = elemType.Elem()
|
||||
}
|
||||
|
||||
if elemType.Name() == "" {
|
||||
return nil, ErrNoName
|
||||
}
|
||||
|
||||
return &listSink{
|
||||
node: node,
|
||||
ref: ref,
|
||||
isPtr: sliceType.Elem().Kind() == reflect.Ptr,
|
||||
elemType: elemType,
|
||||
name: elemType.Name(),
|
||||
results: reflect.MakeSlice(reflect.Indirect(ref).Type(), 0, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type listSink struct {
|
||||
node Node
|
||||
ref reflect.Value
|
||||
results reflect.Value
|
||||
elemType reflect.Type
|
||||
name string
|
||||
isPtr bool
|
||||
idx int
|
||||
}
|
||||
|
||||
func (l *listSink) slice() reflect.Value {
|
||||
return l.results
|
||||
}
|
||||
|
||||
func (l *listSink) setSlice(s reflect.Value) {
|
||||
l.results = s
|
||||
}
|
||||
|
||||
func (l *listSink) reset() {
|
||||
l.results = reflect.MakeSlice(reflect.Indirect(l.ref).Type(), 0, 0)
|
||||
}
|
||||
|
||||
func (l *listSink) elem() reflect.Value {
|
||||
if l.results.IsValid() && l.idx < l.results.Len() {
|
||||
return l.results.Index(l.idx).Addr()
|
||||
}
|
||||
return reflect.New(l.elemType)
|
||||
}
|
||||
|
||||
func (l *listSink) bucketName() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
func (l *listSink) add(i *item) error {
|
||||
if l.idx == l.results.Len() {
|
||||
if l.isPtr {
|
||||
l.results = reflect.Append(l.results, *i.value)
|
||||
} else {
|
||||
l.results = reflect.Append(l.results, reflect.Indirect(*i.value))
|
||||
}
|
||||
}
|
||||
|
||||
l.idx++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *listSink) flush() error {
|
||||
if l.results.IsValid() && l.results.Len() > 0 {
|
||||
reflect.Indirect(l.ref).Set(l.results)
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
func (l *listSink) readOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func newFirstSink(node Node, to interface{}) (*firstSink, error) {
|
||||
ref := reflect.ValueOf(to)
|
||||
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Struct {
|
||||
return nil, ErrStructPtrNeeded
|
||||
}
|
||||
|
||||
return &firstSink{
|
||||
node: node,
|
||||
ref: ref,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type firstSink struct {
|
||||
node Node
|
||||
ref reflect.Value
|
||||
found bool
|
||||
}
|
||||
|
||||
func (f *firstSink) elem() reflect.Value {
|
||||
return reflect.New(reflect.Indirect(f.ref).Type())
|
||||
}
|
||||
|
||||
func (f *firstSink) bucketName() string {
|
||||
return reflect.Indirect(f.ref).Type().Name()
|
||||
}
|
||||
|
||||
func (f *firstSink) add(i *item) error {
|
||||
reflect.Indirect(f.ref).Set(i.value.Elem())
|
||||
f.found = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firstSink) flush() error {
|
||||
if !f.found {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firstSink) readOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func newDeleteSink(node Node, kind interface{}) (*deleteSink, error) {
|
||||
ref := reflect.ValueOf(kind)
|
||||
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Struct {
|
||||
return nil, ErrStructPtrNeeded
|
||||
}
|
||||
|
||||
return &deleteSink{
|
||||
node: node,
|
||||
ref: ref,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type deleteSink struct {
|
||||
node Node
|
||||
ref reflect.Value
|
||||
removed int
|
||||
}
|
||||
|
||||
func (d *deleteSink) elem() reflect.Value {
|
||||
return reflect.New(reflect.Indirect(d.ref).Type())
|
||||
}
|
||||
|
||||
func (d *deleteSink) bucketName() string {
|
||||
return reflect.Indirect(d.ref).Type().Name()
|
||||
}
|
||||
|
||||
func (d *deleteSink) add(i *item) error {
|
||||
info, err := extract(&d.ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for fieldName, fieldCfg := range info.Fields {
|
||||
if fieldCfg.Index == "" {
|
||||
continue
|
||||
}
|
||||
idx, err := getIndex(i.bucket, fieldCfg.Index, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = idx.RemoveID(i.k)
|
||||
if err != nil {
|
||||
if err == index.ErrNotFound {
|
||||
return ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.removed++
|
||||
return i.bucket.Delete(i.k)
|
||||
}
|
||||
|
||||
func (d *deleteSink) flush() error {
|
||||
if d.removed == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *deleteSink) readOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func newCountSink(node Node, kind interface{}) (*countSink, error) {
|
||||
ref := reflect.ValueOf(kind)
|
||||
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Struct {
|
||||
return nil, ErrStructPtrNeeded
|
||||
}
|
||||
|
||||
return &countSink{
|
||||
node: node,
|
||||
ref: ref,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type countSink struct {
|
||||
node Node
|
||||
ref reflect.Value
|
||||
counter int
|
||||
}
|
||||
|
||||
func (c *countSink) elem() reflect.Value {
|
||||
return reflect.New(reflect.Indirect(c.ref).Type())
|
||||
}
|
||||
|
||||
func (c *countSink) bucketName() string {
|
||||
return reflect.Indirect(c.ref).Type().Name()
|
||||
}
|
||||
|
||||
func (c *countSink) add(i *item) error {
|
||||
c.counter++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *countSink) flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *countSink) readOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func newRawSink() *rawSink {
|
||||
return &rawSink{}
|
||||
}
|
||||
|
||||
type rawSink struct {
|
||||
results [][]byte
|
||||
execFn func([]byte, []byte) error
|
||||
}
|
||||
|
||||
func (r *rawSink) add(i *item) error {
|
||||
if r.execFn != nil {
|
||||
err := r.execFn(i.k, i.v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
r.results = append(r.results, i.v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rawSink) bucketName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *rawSink) flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rawSink) readOnly() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func newEachSink(to interface{}) (*eachSink, error) {
|
||||
ref := reflect.ValueOf(to)
|
||||
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Struct {
|
||||
return nil, ErrStructPtrNeeded
|
||||
}
|
||||
|
||||
return &eachSink{
|
||||
ref: ref,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type eachSink struct {
|
||||
ref reflect.Value
|
||||
execFn func(interface{}) error
|
||||
}
|
||||
|
||||
func (e *eachSink) elem() reflect.Value {
|
||||
return reflect.New(reflect.Indirect(e.ref).Type())
|
||||
}
|
||||
|
||||
func (e *eachSink) bucketName() string {
|
||||
return reflect.Indirect(e.ref).Type().Name()
|
||||
}
|
||||
|
||||
func (e *eachSink) add(i *item) error {
|
||||
return e.execFn(i.value.Interface())
|
||||
}
|
||||
|
||||
func (e *eachSink) flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *eachSink) readOnly() bool {
|
||||
return true
|
||||
}
|
22
vendor/github.com/asdine/storm/v3/sink_sorter_swap.go
generated
vendored
Normal file
22
vendor/github.com/asdine/storm/v3/sink_sorter_swap.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// +build !go1.8
|
||||
|
||||
package storm
|
||||
|
||||
import "reflect"
|
||||
|
||||
func (s *sorter) Swap(i, j int) {
|
||||
// skip if we encountered an earlier error
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if ssink, ok := s.sink.(sliceSink); ok {
|
||||
x, y := ssink.slice().Index(i).Interface(), ssink.slice().Index(j).Interface()
|
||||
ssink.slice().Index(i).Set(reflect.ValueOf(y))
|
||||
ssink.slice().Index(j).Set(reflect.ValueOf(x))
|
||||
} else {
|
||||
s.list[i], s.list[j] = s.list[j], s.list[i]
|
||||
}
|
||||
}
|
20
vendor/github.com/asdine/storm/v3/sink_sorter_swap_go1.8.go
generated
vendored
Normal file
20
vendor/github.com/asdine/storm/v3/sink_sorter_swap_go1.8.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// +build go1.8
|
||||
|
||||
package storm
|
||||
|
||||
import "reflect"
|
||||
|
||||
func (s *sorter) Swap(i, j int) {
|
||||
// skip if we encountered an earlier error
|
||||
select {
|
||||
case <-s.done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if ssink, ok := s.sink.(sliceSink); ok {
|
||||
reflect.Swapper(ssink.slice().Interface())(i, j)
|
||||
} else {
|
||||
s.list[i], s.list[j] = s.list[j], s.list[i]
|
||||
}
|
||||
}
|
425
vendor/github.com/asdine/storm/v3/store.go
generated
vendored
Normal file
425
vendor/github.com/asdine/storm/v3/store.go
generated
vendored
Normal file
@ -0,0 +1,425 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
|
||||
"github.com/asdine/storm/v3/index"
|
||||
"github.com/asdine/storm/v3/q"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// TypeStore stores user defined types in BoltDB.
|
||||
type TypeStore interface {
|
||||
Finder
|
||||
// Init creates the indexes and buckets for a given structure
|
||||
Init(data interface{}) error
|
||||
|
||||
// ReIndex rebuilds all the indexes of a bucket
|
||||
ReIndex(data interface{}) error
|
||||
|
||||
// Save a structure
|
||||
Save(data interface{}) error
|
||||
|
||||
// Update a structure
|
||||
Update(data interface{}) error
|
||||
|
||||
// UpdateField updates a single field
|
||||
UpdateField(data interface{}, fieldName string, value interface{}) error
|
||||
|
||||
// Drop a bucket
|
||||
Drop(data interface{}) error
|
||||
|
||||
// DeleteStruct deletes a structure from the associated bucket
|
||||
DeleteStruct(data interface{}) error
|
||||
}
|
||||
|
||||
// Init creates the indexes and buckets for a given structure
|
||||
func (n *node) Init(data interface{}) error {
|
||||
v := reflect.ValueOf(data)
|
||||
cfg, err := extract(&v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readWriteTx(func(tx *bolt.Tx) error {
|
||||
return n.init(tx, cfg)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) init(tx *bolt.Tx, cfg *structConfig) error {
|
||||
bucket, err := n.CreateBucketIfNotExists(tx, cfg.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save node configuration in the bucket
|
||||
_, err = newMeta(bucket, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for fieldName, fieldCfg := range cfg.Fields {
|
||||
if fieldCfg.Index == "" {
|
||||
continue
|
||||
}
|
||||
switch fieldCfg.Index {
|
||||
case tagUniqueIdx:
|
||||
_, err = index.NewUniqueIndex(bucket, []byte(indexPrefix+fieldName))
|
||||
case tagIdx:
|
||||
_, err = index.NewListIndex(bucket, []byte(indexPrefix+fieldName))
|
||||
default:
|
||||
err = ErrIdxNotFound
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) ReIndex(data interface{}) error {
|
||||
ref := reflect.ValueOf(data)
|
||||
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Struct {
|
||||
return ErrStructPtrNeeded
|
||||
}
|
||||
|
||||
cfg, err := extract(&ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readWriteTx(func(tx *bolt.Tx) error {
|
||||
return n.reIndex(tx, data, cfg)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) reIndex(tx *bolt.Tx, data interface{}, cfg *structConfig) error {
|
||||
root := n.WithTransaction(tx)
|
||||
nodes := root.From(cfg.Name).PrefixScan(indexPrefix)
|
||||
bucket := root.GetBucket(tx, cfg.Name)
|
||||
if bucket == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
buckets := node.Bucket()
|
||||
name := buckets[len(buckets)-1]
|
||||
err := bucket.DeleteBucket([]byte(name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
total, err := root.Count(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := 0; i < total; i++ {
|
||||
err = root.Select(q.True()).Skip(i).First(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = root.Update(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save a structure
|
||||
func (n *node) Save(data interface{}) error {
|
||||
ref := reflect.ValueOf(data)
|
||||
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Struct {
|
||||
return ErrStructPtrNeeded
|
||||
}
|
||||
|
||||
cfg, err := extract(&ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ID.IsZero {
|
||||
if !cfg.ID.IsInteger || !cfg.ID.Increment {
|
||||
return ErrZeroID
|
||||
}
|
||||
}
|
||||
|
||||
return n.readWriteTx(func(tx *bolt.Tx) error {
|
||||
return n.save(tx, cfg, data, false)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) save(tx *bolt.Tx, cfg *structConfig, data interface{}, update bool) error {
|
||||
bucket, err := n.CreateBucketIfNotExists(tx, cfg.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save node configuration in the bucket
|
||||
meta, err := newMeta(bucket, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ID.IsZero {
|
||||
err = meta.increment(cfg.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
id, err := toBytes(cfg.ID.Value.Interface(), n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for fieldName, fieldCfg := range cfg.Fields {
|
||||
if !update && !fieldCfg.IsID && fieldCfg.Increment && fieldCfg.IsInteger && fieldCfg.IsZero {
|
||||
err = meta.increment(fieldCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if fieldCfg.Index == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
idx, err := getIndex(bucket, fieldCfg.Index, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if update && fieldCfg.IsZero && !fieldCfg.ForceUpdate {
|
||||
continue
|
||||
}
|
||||
|
||||
if fieldCfg.IsZero {
|
||||
err = idx.RemoveID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
value, err := toBytes(fieldCfg.Value.Interface(), n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var found bool
|
||||
idsSaved, err := idx.All(value, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, idSaved := range idsSaved {
|
||||
if bytes.Compare(idSaved, id) == 0 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
|
||||
err = idx.RemoveID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = idx.Add(value, id)
|
||||
if err != nil {
|
||||
if err == index.ErrAlreadyExists {
|
||||
return ErrAlreadyExists
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
raw, err := n.codec.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bucket.Put(id, raw)
|
||||
}
|
||||
|
||||
// Update a structure
|
||||
func (n *node) Update(data interface{}) error {
|
||||
return n.update(data, func(ref *reflect.Value, current *reflect.Value, cfg *structConfig) error {
|
||||
numfield := ref.NumField()
|
||||
for i := 0; i < numfield; i++ {
|
||||
f := ref.Field(i)
|
||||
if ref.Type().Field(i).PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
zero := reflect.Zero(f.Type()).Interface()
|
||||
actual := f.Interface()
|
||||
if !reflect.DeepEqual(actual, zero) {
|
||||
cf := current.Field(i)
|
||||
cf.Set(f)
|
||||
idxInfo, ok := cfg.Fields[ref.Type().Field(i).Name]
|
||||
if ok {
|
||||
idxInfo.Value = &cf
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateField updates a single field
|
||||
func (n *node) UpdateField(data interface{}, fieldName string, value interface{}) error {
|
||||
return n.update(data, func(ref *reflect.Value, current *reflect.Value, cfg *structConfig) error {
|
||||
f := current.FieldByName(fieldName)
|
||||
if !f.IsValid() {
|
||||
return ErrNotFound
|
||||
}
|
||||
tf, _ := current.Type().FieldByName(fieldName)
|
||||
if tf.PkgPath != "" {
|
||||
return ErrNotFound
|
||||
}
|
||||
v := reflect.ValueOf(value)
|
||||
if v.Kind() != f.Kind() {
|
||||
return ErrIncompatibleValue
|
||||
}
|
||||
f.Set(v)
|
||||
idxInfo, ok := cfg.Fields[fieldName]
|
||||
if ok {
|
||||
idxInfo.Value = &f
|
||||
idxInfo.IsZero = isZero(idxInfo.Value)
|
||||
idxInfo.ForceUpdate = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) update(data interface{}, fn func(*reflect.Value, *reflect.Value, *structConfig) error) error {
|
||||
ref := reflect.ValueOf(data)
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Struct {
|
||||
return ErrStructPtrNeeded
|
||||
}
|
||||
|
||||
cfg, err := extract(&ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ID.IsZero {
|
||||
return ErrNoID
|
||||
}
|
||||
|
||||
current := reflect.New(reflect.Indirect(ref).Type())
|
||||
|
||||
return n.readWriteTx(func(tx *bolt.Tx) error {
|
||||
err = n.WithTransaction(tx).One(cfg.ID.Name, cfg.ID.Value.Interface(), current.Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ref := reflect.ValueOf(data).Elem()
|
||||
cref := current.Elem()
|
||||
err = fn(&ref, &cref, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.save(tx, cfg, current.Interface(), true)
|
||||
})
|
||||
}
|
||||
|
||||
// Drop a bucket
|
||||
func (n *node) Drop(data interface{}) error {
|
||||
var bucketName string
|
||||
|
||||
v := reflect.ValueOf(data)
|
||||
if v.Kind() != reflect.String {
|
||||
info, err := extract(&v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucketName = info.Name
|
||||
} else {
|
||||
bucketName = v.Interface().(string)
|
||||
}
|
||||
|
||||
return n.readWriteTx(func(tx *bolt.Tx) error {
|
||||
return n.drop(tx, bucketName)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) drop(tx *bolt.Tx, bucketName string) error {
|
||||
bucket := n.GetBucket(tx)
|
||||
if bucket == nil {
|
||||
return tx.DeleteBucket([]byte(bucketName))
|
||||
}
|
||||
|
||||
return bucket.DeleteBucket([]byte(bucketName))
|
||||
}
|
||||
|
||||
// DeleteStruct deletes a structure from the associated bucket
|
||||
func (n *node) DeleteStruct(data interface{}) error {
|
||||
ref := reflect.ValueOf(data)
|
||||
|
||||
if !ref.IsValid() || ref.Kind() != reflect.Ptr || ref.Elem().Kind() != reflect.Struct {
|
||||
return ErrStructPtrNeeded
|
||||
}
|
||||
|
||||
cfg, err := extract(&ref)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, err := toBytes(cfg.ID.Value.Interface(), n.codec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.readWriteTx(func(tx *bolt.Tx) error {
|
||||
return n.deleteStruct(tx, cfg, id)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *node) deleteStruct(tx *bolt.Tx, cfg *structConfig, id []byte) error {
|
||||
bucket := n.GetBucket(tx, cfg.Name)
|
||||
if bucket == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
for fieldName, fieldCfg := range cfg.Fields {
|
||||
if fieldCfg.Index == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
idx, err := getIndex(bucket, fieldCfg.Index, fieldName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = idx.RemoveID(id)
|
||||
if err != nil {
|
||||
if err == index.ErrNotFound {
|
||||
return ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
raw := bucket.Get(id)
|
||||
if raw == nil {
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
return bucket.Delete(id)
|
||||
}
|
142
vendor/github.com/asdine/storm/v3/storm.go
generated
vendored
Normal file
142
vendor/github.com/asdine/storm/v3/storm.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
package storm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"github.com/asdine/storm/v3/codec"
|
||||
"github.com/asdine/storm/v3/codec/json"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
const (
|
||||
dbinfo = "__storm_db"
|
||||
metadataBucket = "__storm_metadata"
|
||||
)
|
||||
|
||||
// Defaults to json
|
||||
var defaultCodec = json.Codec
|
||||
|
||||
// Open opens a database at the given path with optional Storm options.
|
||||
func Open(path string, stormOptions ...func(*Options) error) (*DB, error) {
|
||||
var err error
|
||||
|
||||
var opts Options
|
||||
for _, option := range stormOptions {
|
||||
if err = option(&opts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
s := DB{
|
||||
Bolt: opts.bolt,
|
||||
}
|
||||
|
||||
n := node{
|
||||
s: &s,
|
||||
codec: opts.codec,
|
||||
batchMode: opts.batchMode,
|
||||
rootBucket: opts.rootBucket,
|
||||
}
|
||||
|
||||
if n.codec == nil {
|
||||
n.codec = defaultCodec
|
||||
}
|
||||
|
||||
if opts.boltMode == 0 {
|
||||
opts.boltMode = 0600
|
||||
}
|
||||
|
||||
if opts.boltOptions == nil {
|
||||
opts.boltOptions = &bolt.Options{Timeout: 1 * time.Second}
|
||||
}
|
||||
|
||||
s.Node = &n
|
||||
|
||||
// skip if UseDB option is used
|
||||
if s.Bolt == nil {
|
||||
s.Bolt, err = bolt.Open(path, opts.boltMode, opts.boltOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = s.checkVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// DB is the wrapper around BoltDB. It contains an instance of BoltDB and uses it to perform all the
|
||||
// needed operations
|
||||
type DB struct {
|
||||
// The root node that points to the root bucket.
|
||||
Node
|
||||
|
||||
// Bolt is still easily accessible
|
||||
Bolt *bolt.DB
|
||||
}
|
||||
|
||||
// Close the database
|
||||
func (s *DB) Close() error {
|
||||
return s.Bolt.Close()
|
||||
}
|
||||
|
||||
func (s *DB) checkVersion() error {
|
||||
var v string
|
||||
err := s.Get(dbinfo, "version", &v)
|
||||
if err != nil && err != ErrNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
// for now, we only set the current version if it doesn't exist.
|
||||
// v1 and v2 database files are compatible.
|
||||
if v == "" {
|
||||
return s.Set(dbinfo, "version", Version)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// toBytes turns an interface into a slice of bytes
|
||||
func toBytes(key interface{}, codec codec.MarshalUnmarshaler) ([]byte, error) {
|
||||
if key == nil {
|
||||
return nil, nil
|
||||
}
|
||||
switch t := key.(type) {
|
||||
case []byte:
|
||||
return t, nil
|
||||
case string:
|
||||
return []byte(t), nil
|
||||
case int:
|
||||
return numbertob(int64(t))
|
||||
case uint:
|
||||
return numbertob(uint64(t))
|
||||
case int8, int16, int32, int64, uint8, uint16, uint32, uint64:
|
||||
return numbertob(t)
|
||||
default:
|
||||
return codec.Marshal(key)
|
||||
}
|
||||
}
|
||||
|
||||
func numbertob(v interface{}) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
err := binary.Write(&buf, binary.BigEndian, v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func numberfromb(raw []byte) (int64, error) {
|
||||
r := bytes.NewReader(raw)
|
||||
var to int64
|
||||
err := binary.Read(r, binary.BigEndian, &to)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return to, nil
|
||||
}
|
52
vendor/github.com/asdine/storm/v3/transaction.go
generated
vendored
Normal file
52
vendor/github.com/asdine/storm/v3/transaction.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
package storm
|
||||
|
||||
import bolt "go.etcd.io/bbolt"
|
||||
|
||||
// Tx is a transaction.
|
||||
type Tx interface {
|
||||
// Commit writes all changes to disk.
|
||||
Commit() error
|
||||
|
||||
// Rollback closes the transaction and ignores all previous updates.
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
// Begin starts a new transaction.
|
||||
func (n node) Begin(writable bool) (Node, error) {
|
||||
var err error
|
||||
|
||||
n.tx, err = n.s.Bolt.Begin(writable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &n, nil
|
||||
}
|
||||
|
||||
// Rollback closes the transaction and ignores all previous updates.
|
||||
func (n *node) Rollback() error {
|
||||
if n.tx == nil {
|
||||
return ErrNotInTransaction
|
||||
}
|
||||
|
||||
err := n.tx.Rollback()
|
||||
if err == bolt.ErrTxClosed {
|
||||
return ErrNotInTransaction
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit writes all changes to disk.
|
||||
func (n *node) Commit() error {
|
||||
if n.tx == nil {
|
||||
return ErrNotInTransaction
|
||||
}
|
||||
|
||||
err := n.tx.Commit()
|
||||
if err == bolt.ErrTxClosed {
|
||||
return ErrNotInTransaction
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
4
vendor/github.com/asdine/storm/v3/version.go
generated
vendored
Normal file
4
vendor/github.com/asdine/storm/v3/version.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
package storm
|
||||
|
||||
// Version of Storm
|
||||
const Version = "2.0.0"
|
Reference in New Issue
Block a user