forked from mirrors/gitea
add other session providers (#5963)
This commit is contained in:
parent
bf4badad1d
commit
9de871a0f8
111
Gopkg.lock
generated
111
Gopkg.lock
generated
@ -17,6 +17,14 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "d5a42771e7e851e8a89c5c6ffa0f5b075342f9df"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5d72bbcc9c8667b11c3dc3cbe681c5a6f71e5096744c0bf7726ab5c6425d5dc4"
|
||||
name = "github.com/BurntSushi/toml"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005"
|
||||
version = "v0.3.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3fcef06a1a6561955c94af6c7757a6fa37605eb653f0d06ab960e5bb80092195"
|
||||
name = "github.com/PuerkitoBio/goquery"
|
||||
@ -185,6 +193,28 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "098da33fde5f9220736531b3cb26a2dec86a8367"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:eb205556fe75307c6d2b58d4159e7c2da23e2666481d352c66d4055bebf45a3c"
|
||||
name = "github.com/couchbase/gomemcached"
|
||||
packages = [
|
||||
".",
|
||||
"client",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "5125a94a666c83cb9b7a60907833cd320b84c20f"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ea03e12e246f7708a7b7ab3ad04e96d21ce73f48bb56258bc2bffeed474212e6"
|
||||
name = "github.com/couchbase/goutils"
|
||||
packages = [
|
||||
"logging",
|
||||
"scramsha",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "e865a1461c8ac0032bd37e2d4dab3289faea3873"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:82e1ad11d777f7bff9a1fc678a8a534a318f85e5026a8a4d6f4a94a6b0678bb6"
|
||||
@ -197,6 +227,14 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "eb6ae3743b3f300f2136f83ca78c08cc071edbd4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:df592f4b82b993fcac270862376c34210776b8b0334a0f59f4d9d80467713ffa"
|
||||
name = "github.com/couchbaselabs/go-couchbase"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d904413d884d1fb849e2ad8834619f661761ef57"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
@ -358,14 +396,19 @@
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:8fea5718d84af17762195beb6fe92a0d6c1048452a1dbc464d227f12e0cff0cc"
|
||||
digest = "1:a26b7b56aece087165b8db87afb05db8495252449553ca20d15f5a24202f36bc"
|
||||
name = "github.com/go-macaron/session"
|
||||
packages = [
|
||||
".",
|
||||
"couchbase",
|
||||
"memcache",
|
||||
"mysql",
|
||||
"nodb",
|
||||
"postgres",
|
||||
"redis",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "330e4e4d8beb7b00111ac34539561f46f94c4458"
|
||||
revision = "0a0a789bf1934e55fde19629869caa015a40c525"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:758d2371fcdee6d02565901b348729053c636055e67ef6e17aa466c7ff6cc57c"
|
||||
@ -579,6 +622,28 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "e3534c89ef969912856dfa39e56b09e58c5f5daf"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1e6a29ed1f189354030e3371f63ec58aacbc2bf232fd104c6e0d41174ac5af48"
|
||||
name = "github.com/lunny/log"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "7887c61bf0de75586961948b286be6f7d05d9f58"
|
||||
version = "v0.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:683d835728cb95d176d423b522420eb5e4ec859b276bca18466476b82b3ebc4c"
|
||||
name = "github.com/lunny/nodb"
|
||||
packages = [
|
||||
".",
|
||||
"config",
|
||||
"store",
|
||||
"store/driver",
|
||||
"store/goleveldb",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "fc1ef06ad4af0da31cdb87e3fa5ec084c67e6597"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:aa7dcd6a0db70d514821f8739d0a22e7df33b499d8d399cf15b2858d44f8319e"
|
||||
name = "github.com/markbates/goth"
|
||||
@ -682,6 +747,14 @@
|
||||
revision = "bb6d471dc95d4fe11e432687f8b70ff496cf3136"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:14715f705ff5dfe0ffd6571d7d201dd8e921030f8070321a79380d8ca4ec1a24"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4"
|
||||
version = "v0.8.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
@ -775,6 +848,14 @@
|
||||
pruneopts = "NUT"
|
||||
revision = "1dba4b3954bc059efc3991ec364f9f9a35f597d2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:dbda803f21e60c38de7d9f884390f2ebbe234ce0c3d139b65bbb36b03a99d266"
|
||||
name = "github.com/siddontang/go-snappy"
|
||||
packages = ["snappy"]
|
||||
pruneopts = "NUT"
|
||||
revision = "d8f7bb82a96d89c1254e5a6c967134e1433c9ee2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:89fd77d603a74a6540d60067debad9397865bf040955d907362c95d364baeba6"
|
||||
name = "github.com/src-d/gcfg"
|
||||
@ -804,6 +885,27 @@
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:685fdfea42d825ebd39ee0994354b46c374cf2c2b2d97a41a8dee1807c6a9b62"
|
||||
name = "github.com/syndtr/goleveldb"
|
||||
packages = [
|
||||
"leveldb",
|
||||
"leveldb/cache",
|
||||
"leveldb/comparer",
|
||||
"leveldb/errors",
|
||||
"leveldb/filter",
|
||||
"leveldb/iterator",
|
||||
"leveldb/journal",
|
||||
"leveldb/memdb",
|
||||
"leveldb/opt",
|
||||
"leveldb/storage",
|
||||
"leveldb/table",
|
||||
"leveldb/util",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "2f17a3356c6616cbfc4ae4c38147dc062a68fb0e"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:3cb6dfe7cdece5716b1c3c3c0b5faf7fce2e83e2758e2baad2e9986d101980b8"
|
||||
@ -1150,6 +1252,11 @@
|
||||
"github.com/go-macaron/i18n",
|
||||
"github.com/go-macaron/inject",
|
||||
"github.com/go-macaron/session",
|
||||
"github.com/go-macaron/session/couchbase",
|
||||
"github.com/go-macaron/session/memcache",
|
||||
"github.com/go-macaron/session/mysql",
|
||||
"github.com/go-macaron/session/nodb",
|
||||
"github.com/go-macaron/session/postgres",
|
||||
"github.com/go-macaron/session/redis",
|
||||
"github.com/go-macaron/toolbox",
|
||||
"github.com/go-sql-driver/mysql",
|
||||
|
@ -250,7 +250,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
|
||||
|
||||
## Session (`session`)
|
||||
|
||||
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, mysql\].
|
||||
- `PROVIDER`: **memory**: Session engine provider \[memory, file, redis, mysql, couchbase, memcache, nodb, postgres\].
|
||||
- `PROVIDER_CONFIG`: **data/sessions**: For file, the root path; for others, the connection string.
|
||||
- `COOKIE_SECURE`: **false**: Enable this to force using HTTPS for all session access.
|
||||
- `COOKIE_NAME`: **i\_like\_gitea**: The name of the cookie used for the session ID.
|
||||
|
@ -31,7 +31,12 @@ import (
|
||||
_ "github.com/go-macaron/cache/memcache" // memcache plugin for cache
|
||||
_ "github.com/go-macaron/cache/redis"
|
||||
"github.com/go-macaron/session"
|
||||
_ "github.com/go-macaron/session/redis" // redis plugin for store session
|
||||
_ "github.com/go-macaron/session/couchbase" // couchbase plugin for session store
|
||||
_ "github.com/go-macaron/session/memcache" // memcache plugin for session store
|
||||
_ "github.com/go-macaron/session/mysql" // mysql plugin for session store
|
||||
_ "github.com/go-macaron/session/nodb" // nodb plugin for session store
|
||||
_ "github.com/go-macaron/session/postgres" // postgres plugin for session store
|
||||
_ "github.com/go-macaron/session/redis" // redis plugin for store session
|
||||
"github.com/go-xorm/core"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
version "github.com/mcuadros/go-version"
|
||||
@ -1506,7 +1511,7 @@ func newCacheService() {
|
||||
|
||||
func newSessionService() {
|
||||
SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory",
|
||||
[]string{"memory", "file", "redis", "mysql"})
|
||||
[]string{"memory", "file", "redis", "mysql", "postgres", "couchbase", "memcache", "nodb"})
|
||||
SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").MustString(path.Join(AppDataPath, "sessions")), "\" ")
|
||||
if SessionConfig.Provider == "file" && !filepath.IsAbs(SessionConfig.ProviderConfig) {
|
||||
SessionConfig.ProviderConfig = path.Join(AppWorkPath, SessionConfig.ProviderConfig)
|
||||
|
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
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.
|
21
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
generated
vendored
Normal file
21
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
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.
|
21
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
generated
vendored
Normal file
21
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
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.
|
21
vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
generated
vendored
Normal file
21
vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
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.
|
509
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
509
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
@ -0,0 +1,509 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func e(format string, args ...interface{}) error {
|
||||
return fmt.Errorf("toml: "+format, args...)
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||
// TOML description of themselves.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalTOML(interface{}) error
|
||||
}
|
||||
|
||||
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
||||
func Unmarshal(p []byte, v interface{}) error {
|
||||
_, err := Decode(string(p), v)
|
||||
return err
|
||||
}
|
||||
|
||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||||
// When using the various `Decode*` functions, the type `Primitive` may
|
||||
// be given to any value, and its decoding will be delayed.
|
||||
//
|
||||
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
|
||||
//
|
||||
// The underlying representation of a `Primitive` value is subject to change.
|
||||
// Do not rely on it.
|
||||
//
|
||||
// N.B. Primitive values are still parsed, so using them will only avoid
|
||||
// the overhead of reflection. They can be useful when you don't know the
|
||||
// exact type of TOML data until run time.
|
||||
type Primitive struct {
|
||||
undecoded interface{}
|
||||
context Key
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
//
|
||||
// Use MetaData.PrimitiveDecode instead.
|
||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md := MetaData{decoded: make(map[string]bool)}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
||||
// decodes a TOML value that has already been parsed. Valid primitive values
|
||||
// can *only* be obtained from values filled by the decoder functions,
|
||||
// including this method. (i.e., `v` may contain more `Primitive`
|
||||
// values.)
|
||||
//
|
||||
// Meta data for primitive values is included in the meta data returned by
|
||||
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
||||
// method will only reflect keys that were decoded. Namely, any keys hidden
|
||||
// behind a Primitive will be considered undecoded. Executing this method will
|
||||
// update the undecoded keys in the meta data. (See the example.)
|
||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md.context = primValue.context
|
||||
defer func() { md.context = nil }()
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// Decode will decode the contents of `data` in TOML format into a pointer
|
||||
// `v`.
|
||||
//
|
||||
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
|
||||
// used interchangeably.)
|
||||
//
|
||||
// TOML arrays of tables correspond to either a slice of structs or a slice
|
||||
// of maps.
|
||||
//
|
||||
// TOML datetimes correspond to Go `time.Time` values.
|
||||
//
|
||||
// All other TOML types (float, string, int, bool and array) correspond
|
||||
// to the obvious Go types.
|
||||
//
|
||||
// An exception to the above rules is if a type implements the
|
||||
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
|
||||
// (floats, strings, integers, booleans and datetimes) will be converted to
|
||||
// a byte string and given to the value's UnmarshalText method. See the
|
||||
// Unmarshaler example for a demonstration with time duration strings.
|
||||
//
|
||||
// Key mapping
|
||||
//
|
||||
// TOML keys can map to either keys in a Go map or field names in a Go
|
||||
// struct. The special `toml` struct tag may be used to map TOML keys to
|
||||
// struct fields that don't match the key name exactly. (See the example.)
|
||||
// A case insensitive match to struct names will be tried if an exact match
|
||||
// can't be found.
|
||||
//
|
||||
// The mapping between TOML values and Go values is loose. That is, there
|
||||
// may exist TOML values that cannot be placed into your representation, and
|
||||
// there may be parts of your representation that do not correspond to
|
||||
// TOML values. This loose mapping can be made stricter by using the IsDefined
|
||||
// and/or Undecoded methods on the MetaData returned.
|
||||
//
|
||||
// This decoder will not handle cyclic types. If a cyclic type is passed,
|
||||
// `Decode` will not terminate.
|
||||
func Decode(data string, v interface{}) (MetaData, error) {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
||||
}
|
||||
p, err := parse(data)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
md := MetaData{
|
||||
p.mapping, p.types, p.ordered,
|
||||
make(map[string]bool, len(p.ordered)), nil,
|
||||
}
|
||||
return md, md.unify(p.mapping, indirect(rv))
|
||||
}
|
||||
|
||||
// DecodeFile is just like Decode, except it will automatically read the
|
||||
// contents of the file at `fpath` and decode it for you.
|
||||
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
|
||||
bs, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
return Decode(string(bs), v)
|
||||
}
|
||||
|
||||
// DecodeReader is just like Decode, except it will consume all bytes
|
||||
// from the reader and decode it for you.
|
||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
||||
bs, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
return Decode(string(bs), v)
|
||||
}
|
||||
|
||||
// unify performs a sort of type unification based on the structure of `rv`,
|
||||
// which is the client representation.
|
||||
//
|
||||
// Any type mismatch produces an error. Finding a type that we don't know
|
||||
// how to handle produces an unsupported type error.
|
||||
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||
|
||||
// Special case. Look for a `Primitive` value.
|
||||
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
||||
// Save the undecoded data and the key context into the primitive
|
||||
// value.
|
||||
context := make(Key, len(md.context))
|
||||
copy(context, md.context)
|
||||
rv.Set(reflect.ValueOf(Primitive{
|
||||
undecoded: data,
|
||||
context: context,
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Special case. Unmarshaler Interface support.
|
||||
if rv.CanAddr() {
|
||||
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
||||
return v.UnmarshalTOML(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Special case. Handle time.Time values specifically.
|
||||
// TODO: Remove this code when we decide to drop support for Go 1.1.
|
||||
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
|
||||
// interfaces.
|
||||
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
|
||||
return md.unifyDatetime(data, rv)
|
||||
}
|
||||
|
||||
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
||||
if v, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||
return md.unifyText(data, v)
|
||||
}
|
||||
// BUG(burntsushi)
|
||||
// The behavior here is incorrect whenever a Go type satisfies the
|
||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML
|
||||
// hash or array. In particular, the unmarshaler should only be applied
|
||||
// to primitive TOML values. But at this point, it will be applied to
|
||||
// all kinds of values and produce an incorrect error whenever those values
|
||||
// are hashes or arrays (including arrays of tables).
|
||||
|
||||
k := rv.Kind()
|
||||
|
||||
// laziness
|
||||
if k >= reflect.Int && k <= reflect.Uint64 {
|
||||
return md.unifyInt(data, rv)
|
||||
}
|
||||
switch k {
|
||||
case reflect.Ptr:
|
||||
elem := reflect.New(rv.Type().Elem())
|
||||
err := md.unify(data, reflect.Indirect(elem))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rv.Set(elem)
|
||||
return nil
|
||||
case reflect.Struct:
|
||||
return md.unifyStruct(data, rv)
|
||||
case reflect.Map:
|
||||
return md.unifyMap(data, rv)
|
||||
case reflect.Array:
|
||||
return md.unifyArray(data, rv)
|
||||
case reflect.Slice:
|
||||
return md.unifySlice(data, rv)
|
||||
case reflect.String:
|
||||
return md.unifyString(data, rv)
|
||||
case reflect.Bool:
|
||||
return md.unifyBool(data, rv)
|
||||
case reflect.Interface:
|
||||
// we only support empty interfaces.
|
||||
if rv.NumMethod() > 0 {
|
||||
return e("unsupported type %s", rv.Type())
|
||||
}
|
||||
return md.unifyAnything(data, rv)
|
||||
case reflect.Float32:
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
return md.unifyFloat64(data, rv)
|
||||
}
|
||||
return e("unsupported type %s", rv.Kind())
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if mapping == nil {
|
||||
return nil
|
||||
}
|
||||
return e("type mismatch for %s: expected table but found %T",
|
||||
rv.Type().String(), mapping)
|
||||
}
|
||||
|
||||
for key, datum := range tmap {
|
||||
var f *field
|
||||
fields := cachedTypeFields(rv.Type())
|
||||
for i := range fields {
|
||||
ff := &fields[i]
|
||||
if ff.name == key {
|
||||
f = ff
|
||||
break
|
||||
}
|
||||
if f == nil && strings.EqualFold(ff.name, key) {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
if f != nil {
|
||||
subv := rv
|
||||
for _, i := range f.index {
|
||||
subv = indirect(subv.Field(i))
|
||||
}
|
||||
if isUnifiable(subv) {
|
||||
md.decoded[md.context.add(key).String()] = true
|
||||
md.context = append(md.context, key)
|
||||
if err := md.unify(datum, subv); err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
} else if f.name != "" {
|
||||
// Bad user! No soup for you!
|
||||
return e("cannot write unexported field %s.%s",
|
||||
rv.Type().String(), f.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if tmap == nil {
|
||||
return nil
|
||||
}
|
||||
return badtype("map", mapping)
|
||||
}
|
||||
if rv.IsNil() {
|
||||
rv.Set(reflect.MakeMap(rv.Type()))
|
||||
}
|
||||
for k, v := range tmap {
|
||||
md.decoded[md.context.add(k).String()] = true
|
||||
md.context = append(md.context, k)
|
||||
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||
if err := md.unify(v, rvval); err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
|
||||
rvkey.SetString(k)
|
||||
rv.SetMapIndex(rvkey, rvval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
}
|
||||
sliceLen := datav.Len()
|
||||
if sliceLen != rv.Len() {
|
||||
return e("expected array length %d; got TOML array of length %d",
|
||||
rv.Len(), sliceLen)
|
||||
}
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return badtype("slice", data)
|
||||
}
|
||||
n := datav.Len()
|
||||
if rv.IsNil() || rv.Cap() < n {
|
||||
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||||
}
|
||||
rv.SetLen(n)
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||
sliceLen := data.Len()
|
||||
for i := 0; i < sliceLen; i++ {
|
||||
v := data.Index(i).Interface()
|
||||
sliceval := indirect(rv.Index(i))
|
||||
if err := md.unify(v, sliceval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
||||
if _, ok := data.(time.Time); ok {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
return badtype("time.Time", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||
if s, ok := data.(string); ok {
|
||||
rv.SetString(s)
|
||||
return nil
|
||||
}
|
||||
return badtype("string", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||
if num, ok := data.(float64); ok {
|
||||
switch rv.Kind() {
|
||||
case reflect.Float32:
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
rv.SetFloat(num)
|
||||
default:
|
||||
panic("bug")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("float", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||
if num, ok := data.(int64); ok {
|
||||
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int64:
|
||||
// No bounds checking necessary.
|
||||
case reflect.Int8:
|
||||
if num < math.MinInt8 || num > math.MaxInt8 {
|
||||
return e("value %d is out of range for int8", num)
|
||||
}
|
||||
case reflect.Int16:
|
||||
if num < math.MinInt16 || num > math.MaxInt16 {
|
||||
return e("value %d is out of range for int16", num)
|
||||
}
|
||||
case reflect.Int32:
|
||||
if num < math.MinInt32 || num > math.MaxInt32 {
|
||||
return e("value %d is out of range for int32", num)
|
||||
}
|
||||
}
|
||||
rv.SetInt(num)
|
||||
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
||||
unum := uint64(num)
|
||||
switch rv.Kind() {
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
// No bounds checking necessary.
|
||||
case reflect.Uint8:
|
||||
if num < 0 || unum > math.MaxUint8 {
|
||||
return e("value %d is out of range for uint8", num)
|
||||
}
|
||||
case reflect.Uint16:
|
||||
if num < 0 || unum > math.MaxUint16 {
|
||||
return e("value %d is out of range for uint16", num)
|
||||
}
|
||||
case reflect.Uint32:
|
||||
if num < 0 || unum > math.MaxUint32 {
|
||||
return e("value %d is out of range for uint32", num)
|
||||
}
|
||||
}
|
||||
rv.SetUint(unum)
|
||||
} else {
|
||||
panic("unreachable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return badtype("integer", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||
if b, ok := data.(bool); ok {
|
||||
rv.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
return badtype("boolean", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
||||
var s string
|
||||
switch sdata := data.(type) {
|
||||
case TextMarshaler:
|
||||
text, err := sdata.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case fmt.Stringer:
|
||||
s = sdata.String()
|
||||
case string:
|
||||
s = sdata
|
||||
case bool:
|
||||
s = fmt.Sprintf("%v", sdata)
|
||||
case int64:
|
||||
s = fmt.Sprintf("%d", sdata)
|
||||
case float64:
|
||||
s = fmt.Sprintf("%f", sdata)
|
||||
default:
|
||||
return badtype("primitive (string-like)", data)
|
||||
}
|
||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||
func rvalue(v interface{}) reflect.Value {
|
||||
return indirect(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// indirect returns the value pointed to by a pointer.
|
||||
// Pointers are followed until the value is not a pointer.
|
||||
// New values are allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of
|
||||
// interest to us (like encoding.TextUnmarshaler).
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if v.CanSet() {
|
||||
pv := v.Addr()
|
||||
if _, ok := pv.Interface().(TextUnmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
return indirect(reflect.Indirect(v))
|
||||
}
|
||||
|
||||
func isUnifiable(rv reflect.Value) bool {
|
||||
if rv.CanSet() {
|
||||
return true
|
||||
}
|
||||
if _, ok := rv.Interface().(TextUnmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func badtype(expected string, data interface{}) error {
|
||||
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
||||
}
|
121
vendor/github.com/BurntSushi/toml/decode_meta.go
generated
vendored
Normal file
121
vendor/github.com/BurntSushi/toml/decode_meta.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
package toml
|
||||
|
||||
import "strings"
|
||||
|
||||
// MetaData allows access to meta information about TOML data that may not
|
||||
// be inferrable via reflection. In particular, whether a key has been defined
|
||||
// and the TOML type of a key.
|
||||
type MetaData struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
keys []Key
|
||||
decoded map[string]bool
|
||||
context Key // Used only during decoding.
|
||||
}
|
||||
|
||||
// IsDefined returns true if the key given exists in the TOML data. The key
|
||||
// should be specified hierarchially. e.g.,
|
||||
//
|
||||
// // access the TOML key 'a.b.c'
|
||||
// IsDefined("a", "b", "c")
|
||||
//
|
||||
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
||||
func (md *MetaData) IsDefined(key ...string) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var hash map[string]interface{}
|
||||
var ok bool
|
||||
var hashOrVal interface{} = md.mapping
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||
return false
|
||||
}
|
||||
if hashOrVal, ok = hash[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Type returns a string representation of the type of the key specified.
|
||||
//
|
||||
// Type will return the empty string if given an empty key or a key that
|
||||
// does not exist. Keys are case sensitive.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
fullkey := strings.Join(key, ".")
|
||||
if typ, ok := md.types[fullkey]; ok {
|
||||
return typ.typeString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
|
||||
// to get values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string {
|
||||
return strings.Join(k, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuotedAll() string {
|
||||
var ss []string
|
||||
for i := range k {
|
||||
ss = append(ss, k.maybeQuoted(i))
|
||||
}
|
||||
return strings.Join(ss, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
quote := false
|
||||
for _, c := range k[i] {
|
||||
if !isBareKeyChar(c) {
|
||||
quote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if quote {
|
||||
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
func (k Key) add(piece string) Key {
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
|
||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||
// Each key is itself a slice, where the first element is the top of the
|
||||
// hierarchy and the last is the most specific.
|
||||
//
|
||||
// The list will have the same order as the keys appeared in the TOML data.
|
||||
//
|
||||
// All keys returned are non-empty.
|
||||
func (md *MetaData) Keys() []Key {
|
||||
return md.keys
|
||||
}
|
||||
|
||||
// Undecoded returns all keys that have not been decoded in the order in which
|
||||
// they appear in the original TOML document.
|
||||
//
|
||||
// This includes keys that haven't been decoded because of a Primitive value.
|
||||
// Once the Primitive value is decoded, the keys will be considered decoded.
|
||||
//
|
||||
// Also note that decoding into an empty interface will result in no decoding,
|
||||
// and so no keys will be considered decoded.
|
||||
//
|
||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||
// that do not have a concrete type in your representation.
|
||||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
if !md.decoded[key.String()] {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
}
|
||||
return undecoded
|
||||
}
|
27
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
27
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
Package toml provides facilities for decoding and encoding TOML configuration
|
||||
files via reflection. There is also support for delaying decoding with
|
||||
the Primitive type, and querying the set of keys in a TOML document with the
|
||||
MetaData type.
|
||||
|
||||
The specification implemented: https://github.com/toml-lang/toml
|
||||
|
||||
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
|
||||
whether a file is a valid TOML document. It can also be used to print the
|
||||
type of each key in a TOML document.
|
||||
|
||||
Testing
|
||||
|
||||
There are two important types of tests used for this package. The first is
|
||||
contained inside '*_test.go' files and uses the standard Go unit testing
|
||||
framework. These tests are primarily devoted to holistically testing the
|
||||
decoder and encoder.
|
||||
|
||||
The second type of testing is used to verify the implementation's adherence
|
||||
to the TOML specification. These tests have been factored into their own
|
||||
project: https://github.com/BurntSushi/toml-test
|
||||
|
||||
The reason the tests are in a separate project is so that they can be used by
|
||||
any implementation of TOML. Namely, it is language agnostic.
|
||||
*/
|
||||
package toml
|
568
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
568
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
@ -0,0 +1,568 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tomlEncodeError struct{ error }
|
||||
|
||||
var (
|
||||
errArrayMixedElementTypes = errors.New(
|
||||
"toml: cannot encode array with mixed element types")
|
||||
errArrayNilElement = errors.New(
|
||||
"toml: cannot encode array with nil element")
|
||||
errNonString = errors.New(
|
||||
"toml: cannot encode a map with non-string key type")
|
||||
errAnonNonStruct = errors.New(
|
||||
"toml: cannot encode an anonymous field that is not a struct")
|
||||
errArrayNoTable = errors.New(
|
||||
"toml: TOML array element cannot contain a table")
|
||||
errNoKey = errors.New(
|
||||
"toml: top-level values must be Go maps or structs")
|
||||
errAnything = errors.New("") // used in testing
|
||||
)
|
||||
|
||||
var quotedReplacer = strings.NewReplacer(
|
||||
"\t", "\\t",
|
||||
"\n", "\\n",
|
||||
"\r", "\\r",
|
||||
"\"", "\\\"",
|
||||
"\\", "\\\\",
|
||||
)
|
||||
|
||||
// Encoder controls the encoding of Go values to a TOML document to some
|
||||
// io.Writer.
|
||||
//
|
||||
// The indentation level can be controlled with the Indent field.
|
||||
type Encoder struct {
|
||||
// A single indentation level. By default it is two spaces.
|
||||
Indent string
|
||||
|
||||
// hasWritten is whether we have written any output to w yet.
|
||||
hasWritten bool
|
||||
w *bufio.Writer
|
||||
}
|
||||
|
||||
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
|
||||
// given. By default, a single indentation level is 2 spaces.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: bufio.NewWriter(w),
|
||||
Indent: " ",
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes a TOML representation of the Go value to the underlying
|
||||
// io.Writer. If the value given cannot be encoded to a valid TOML document,
|
||||
// then an error is returned.
|
||||
//
|
||||
// The mapping between Go values and TOML values should be precisely the same
|
||||
// as for the Decode* functions. Similarly, the TextMarshaler interface is
|
||||
// supported by encoding the resulting bytes as strings. (If you want to write
|
||||
// arbitrary binary data then you will need to use something like base64 since
|
||||
// TOML does not have any binary types.)
|
||||
//
|
||||
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
|
||||
// sub-hashes are encoded first.
|
||||
//
|
||||
// If a Go map is encoded, then its keys are sorted alphabetically for
|
||||
// deterministic output. More control over this behavior may be provided if
|
||||
// there is demand for it.
|
||||
//
|
||||
// Encoding Go values without a corresponding TOML representation---like map
|
||||
// types with non-string keys---will cause an error to be returned. Similarly
|
||||
// for mixed arrays/slices, arrays/slices with nil elements, embedded
|
||||
// non-struct types and nested slices containing maps or structs.
|
||||
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
|
||||
// and so is []map[string][]string.)
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
rv := eindirect(reflect.ValueOf(v))
|
||||
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
|
||||
return err
|
||||
}
|
||||
return enc.w.Flush()
|
||||
}
|
||||
|
||||
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if terr, ok := r.(tomlEncodeError); ok {
|
||||
err = terr.error
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
enc.encode(key, rv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||
// Special case. Time needs to be in ISO8601 format.
|
||||
// Special case. If we can marshal the type to text, then we used that.
|
||||
// Basically, this prevents the encoder for handling these types as
|
||||
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time, TextMarshaler:
|
||||
enc.keyEqElement(key, rv)
|
||||
return
|
||||
}
|
||||
|
||||
k := rv.Kind()
|
||||
switch k {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
||||
enc.keyEqElement(key, rv)
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
||||
enc.eArrayOfTables(key, rv)
|
||||
} else {
|
||||
enc.keyEqElement(key, rv)
|
||||
}
|
||||
case reflect.Interface:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Map:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.eTable(key, rv)
|
||||
case reflect.Ptr:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Struct:
|
||||
enc.eTable(key, rv)
|
||||
default:
|
||||
panic(e("unsupported type for key '%s': %s", key, k))
|
||||
}
|
||||
}
|
||||
|
||||
// eElement encodes any value that can be an array element (primitives and
|
||||
// arrays).
|
||||
func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
switch v := rv.Interface().(type) {
|
||||
case time.Time:
|
||||
// Special case time.Time as a primitive. Has to come before
|
||||
// TextMarshaler below because time.Time implements
|
||||
// encoding.TextMarshaler, but we need to always use UTC.
|
||||
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
|
||||
return
|
||||
case TextMarshaler:
|
||||
// Special case. Use text marshaler if it's available for this value.
|
||||
if s, err := v.MarshalText(); err != nil {
|
||||
encPanic(err)
|
||||
} else {
|
||||
enc.writeQuoted(string(s))
|
||||
}
|
||||
return
|
||||
}
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64:
|
||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
||||
reflect.Uint32, reflect.Uint64:
|
||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||
case reflect.Float32:
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
|
||||
case reflect.Float64:
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
|
||||
case reflect.Array, reflect.Slice:
|
||||
enc.eArrayOrSliceElement(rv)
|
||||
case reflect.Interface:
|
||||
enc.eElement(rv.Elem())
|
||||
case reflect.String:
|
||||
enc.writeQuoted(rv.String())
|
||||
default:
|
||||
panic(e("unexpected primitive type: %s", rv.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
// By the TOML spec, all floats must have a decimal with at least one
|
||||
// number on either side.
|
||||
func floatAddDecimal(fstr string) string {
|
||||
if !strings.Contains(fstr, ".") {
|
||||
return fstr + ".0"
|
||||
}
|
||||
return fstr
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeQuoted(s string) {
|
||||
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||
length := rv.Len()
|
||||
enc.wf("[")
|
||||
for i := 0; i < length; i++ {
|
||||
elem := rv.Index(i)
|
||||
enc.eElement(elem)
|
||||
if i != length-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
}
|
||||
enc.wf("]")
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
}
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
trv := rv.Index(i)
|
||||
if isNil(trv) {
|
||||
continue
|
||||
}
|
||||
panicIfInvalidKey(key)
|
||||
enc.newline()
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.newline()
|
||||
enc.eMapOrStruct(key, trv)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||
panicIfInvalidKey(key)
|
||||
if len(key) == 1 {
|
||||
// Output an extra newline between top-level tables.
|
||||
// (The newline isn't written if nothing else has been written though.)
|
||||
enc.newline()
|
||||
}
|
||||
if len(key) > 0 {
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
|
||||
enc.newline()
|
||||
}
|
||||
enc.eMapOrStruct(key, rv)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
|
||||
switch rv := eindirect(rv); rv.Kind() {
|
||||
case reflect.Map:
|
||||
enc.eMap(key, rv)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(key, rv)
|
||||
default:
|
||||
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
|
||||
rt := rv.Type()
|
||||
if rt.Key().Kind() != reflect.String {
|
||||
encPanic(errNonString)
|
||||
}
|
||||
|
||||
// Sort keys so that we have deterministic output. And write keys directly
|
||||
// underneath this key first, before writing sub-structs or sub-maps.
|
||||
var mapKeysDirect, mapKeysSub []string
|
||||
for _, mapKey := range rv.MapKeys() {
|
||||
k := mapKey.String()
|
||||
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
||||
mapKeysSub = append(mapKeysSub, k)
|
||||
} else {
|
||||
mapKeysDirect = append(mapKeysDirect, k)
|
||||
}
|
||||
}
|
||||
|
||||
var writeMapKeys = func(mapKeys []string) {
|
||||
sort.Strings(mapKeys)
|
||||
for _, mapKey := range mapKeys {
|
||||
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
|
||||
if isNil(mrv) {
|
||||
// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
enc.encode(key.add(mapKey), mrv)
|
||||
}
|
||||
}
|
||||
writeMapKeys(mapKeysDirect)
|
||||
writeMapKeys(mapKeysSub)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
||||
// Write keys for fields directly under this key first, because if we write
|
||||
// a field that creates a new table, then all keys under it will be in that
|
||||
// table (not the one we're writing here).
|
||||
rt := rv.Type()
|
||||
var fieldsDirect, fieldsSub [][]int
|
||||
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
// skip unexported fields
|
||||
if f.PkgPath != "" && !f.Anonymous {
|
||||
continue
|
||||
}
|
||||
frv := rv.Field(i)
|
||||
if f.Anonymous {
|
||||
t := f.Type
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
// Treat anonymous struct fields with
|
||||
// tag names as though they are not
|
||||
// anonymous, like encoding/json does.
|
||||
if getOptions(f.Tag).name == "" {
|
||||
addFields(t, frv, f.Index)
|
||||
continue
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if t.Elem().Kind() == reflect.Struct &&
|
||||
getOptions(f.Tag).name == "" {
|
||||
if !frv.IsNil() {
|
||||
addFields(t.Elem(), frv.Elem(), f.Index)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Fall through to the normal field encoding logic below
|
||||
// for non-struct anonymous fields.
|
||||
}
|
||||
}
|
||||
|
||||
if typeIsHash(tomlTypeOfGo(frv)) {
|
||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||
} else {
|
||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||
}
|
||||
}
|
||||
}
|
||||
addFields(rt, rv, nil)
|
||||
|
||||
var writeFields = func(fields [][]int) {
|
||||
for _, fieldIndex := range fields {
|
||||
sft := rt.FieldByIndex(fieldIndex)
|
||||
sf := rv.FieldByIndex(fieldIndex)
|
||||
if isNil(sf) {
|
||||
// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
|
||||
opts := getOptions(sft.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
keyName := sft.Name
|
||||
if opts.name != "" {
|
||||
keyName = opts.name
|
||||
}
|
||||
if opts.omitempty && isEmpty(sf) {
|
||||
continue
|
||||
}
|
||||
if opts.omitzero && isZero(sf) {
|
||||
continue
|
||||
}
|
||||
|
||||
enc.encode(key.add(keyName), sf)
|
||||
}
|
||||
}
|
||||
writeFields(fieldsDirect)
|
||||
writeFields(fieldsSub)
|
||||
}
|
||||
|
||||
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
||||
// used to determine whether the types of array elements are mixed (which is
|
||||
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
||||
// element, and valueIsNil is returned as true.
|
||||
|
||||
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
||||
// no concrete TOML type could be found.
|
||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() {
|
||||
return nil
|
||||
}
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
return tomlBool
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64:
|
||||
return tomlInteger
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return tomlFloat
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
||||
return tomlArrayHash
|
||||
}
|
||||
return tomlArray
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return tomlTypeOfGo(rv.Elem())
|
||||
case reflect.String:
|
||||
return tomlString
|
||||
case reflect.Map:
|
||||
return tomlHash
|
||||
case reflect.Struct:
|
||||
switch rv.Interface().(type) {
|
||||
case time.Time:
|
||||
return tomlDatetime
|
||||
case TextMarshaler:
|
||||
return tomlString
|
||||
default:
|
||||
return tomlHash
|
||||
}
|
||||
default:
|
||||
panic("unexpected reflect.Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
// tomlArrayType returns the element type of a TOML array. The type returned
|
||||
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
||||
// slize). This function may also panic if it finds a type that cannot be
|
||||
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
|
||||
// nested arrays of tables).
|
||||
func tomlArrayType(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
firstType := tomlTypeOfGo(rv.Index(0))
|
||||
if firstType == nil {
|
||||
encPanic(errArrayNilElement)
|
||||
}
|
||||
|
||||
rvlen := rv.Len()
|
||||
for i := 1; i < rvlen; i++ {
|
||||
elem := rv.Index(i)
|
||||
switch elemType := tomlTypeOfGo(elem); {
|
||||
case elemType == nil:
|
||||
encPanic(errArrayNilElement)
|
||||
case !typeEqual(firstType, elemType):
|
||||
encPanic(errArrayMixedElementTypes)
|
||||
}
|
||||
}
|
||||
// If we have a nested array, then we must make sure that the nested
|
||||
// array contains ONLY primitives.
|
||||
// This checks arbitrarily nested arrays.
|
||||
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
|
||||
nest := tomlArrayType(eindirect(rv.Index(0)))
|
||||
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
|
||||
encPanic(errArrayNoTable)
|
||||
}
|
||||
}
|
||||
return firstType
|
||||
}
|
||||
|
||||
type tagOptions struct {
|
||||
skip bool // "-"
|
||||
name string
|
||||
omitempty bool
|
||||
omitzero bool
|
||||
}
|
||||
|
||||
func getOptions(tag reflect.StructTag) tagOptions {
|
||||
t := tag.Get("toml")
|
||||
if t == "-" {
|
||||
return tagOptions{skip: true}
|
||||
}
|
||||
var opts tagOptions
|
||||
parts := strings.Split(t, ",")
|
||||
opts.name = parts[0]
|
||||
for _, s := range parts[1:] {
|
||||
switch s {
|
||||
case "omitempty":
|
||||
opts.omitempty = true
|
||||
case "omitzero":
|
||||
opts.omitzero = true
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func isZero(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return rv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float() == 0.0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmpty(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return rv.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !rv.Bool()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (enc *Encoder) newline() {
|
||||
if enc.hasWritten {
|
||||
enc.wf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
}
|
||||
panicIfInvalidKey(key)
|
||||
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||
enc.eElement(val)
|
||||
enc.newline()
|
||||
}
|
||||
|
||||
func (enc *Encoder) wf(format string, v ...interface{}) {
|
||||
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
}
|
||||
|
||||
func (enc *Encoder) indentStr(key Key) string {
|
||||
return strings.Repeat(enc.Indent, len(key)-1)
|
||||
}
|
||||
|
||||
func encPanic(err error) {
|
||||
panic(tomlEncodeError{err})
|
||||
}
|
||||
|
||||
func eindirect(v reflect.Value) reflect.Value {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return eindirect(v.Elem())
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func panicIfInvalidKey(key Key) {
|
||||
for _, k := range key {
|
||||
if len(k) == 0 {
|
||||
encPanic(e("Key '%s' is not a valid table name. Key names "+
|
||||
"cannot be empty.", key.maybeQuotedAll()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isValidKeyName(s string) bool {
|
||||
return len(s) != 0
|
||||
}
|
19
vendor/github.com/BurntSushi/toml/encoding_types.go
generated
vendored
Normal file
19
vendor/github.com/BurntSushi/toml/encoding_types.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// +build go1.2
|
||||
|
||||
package toml
|
||||
|
||||
// In order to support Go 1.1, we define our own TextMarshaler and
|
||||
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
|
||||
// standard library interfaces.
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
)
|
||||
|
||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||
// so that Go 1.1 can be supported.
|
||||
type TextMarshaler encoding.TextMarshaler
|
||||
|
||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||
// here so that Go 1.1 can be supported.
|
||||
type TextUnmarshaler encoding.TextUnmarshaler
|
18
vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
Normal file
18
vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// +build !go1.2
|
||||
|
||||
package toml
|
||||
|
||||
// These interfaces were introduced in Go 1.2, so we add them manually when
|
||||
// compiling for Go 1.1.
|
||||
|
||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
||||
// so that Go 1.1 can be supported.
|
||||
type TextMarshaler interface {
|
||||
MarshalText() (text []byte, err error)
|
||||
}
|
||||
|
||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
||||
// here so that Go 1.1 can be supported.
|
||||
type TextUnmarshaler interface {
|
||||
UnmarshalText(text []byte) error
|
||||
}
|
953
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
953
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
@ -0,0 +1,953 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota
|
||||
itemNIL // used in the parser to indicate no type
|
||||
itemEOF
|
||||
itemText
|
||||
itemString
|
||||
itemRawString
|
||||
itemMultilineString
|
||||
itemRawMultilineString
|
||||
itemBool
|
||||
itemInteger
|
||||
itemFloat
|
||||
itemDatetime
|
||||
itemArray // the start of an array
|
||||
itemArrayEnd
|
||||
itemTableStart
|
||||
itemTableEnd
|
||||
itemArrayTableStart
|
||||
itemArrayTableEnd
|
||||
itemKeyStart
|
||||
itemCommentStart
|
||||
itemInlineTableStart
|
||||
itemInlineTableEnd
|
||||
)
|
||||
|
||||
const (
|
||||
eof = 0
|
||||
comma = ','
|
||||
tableStart = '['
|
||||
tableEnd = ']'
|
||||
arrayTableStart = '['
|
||||
arrayTableEnd = ']'
|
||||
tableSep = '.'
|
||||
keySep = '='
|
||||
arrayStart = '['
|
||||
arrayEnd = ']'
|
||||
commentStart = '#'
|
||||
stringStart = '"'
|
||||
stringEnd = '"'
|
||||
rawStringStart = '\''
|
||||
rawStringEnd = '\''
|
||||
inlineTableStart = '{'
|
||||
inlineTableEnd = '}'
|
||||
)
|
||||
|
||||
type stateFn func(lx *lexer) stateFn
|
||||
|
||||
type lexer struct {
|
||||
input string
|
||||
start int
|
||||
pos int
|
||||
line int
|
||||
state stateFn
|
||||
items chan item
|
||||
|
||||
// Allow for backing up up to three runes.
|
||||
// This is necessary because TOML contains 3-rune tokens (""" and ''').
|
||||
prevWidths [3]int
|
||||
nprev int // how many of prevWidths are in use
|
||||
// If we emit an eof, we can still back up, but it is not OK to call
|
||||
// next again.
|
||||
atEOF bool
|
||||
|
||||
// A stack of state functions used to maintain context.
|
||||
// The idea is to reuse parts of the state machine in various places.
|
||||
// For example, values can appear at the top level or within arbitrarily
|
||||
// nested arrays. The last state on the stack is used after a value has
|
||||
// been lexed. Similarly for comments.
|
||||
stack []stateFn
|
||||
}
|
||||
|
||||
type item struct {
|
||||
typ itemType
|
||||
val string
|
||||
line int
|
||||
}
|
||||
|
||||
func (lx *lexer) nextItem() item {
|
||||
for {
|
||||
select {
|
||||
case item := <-lx.items:
|
||||
return item
|
||||
default:
|
||||
lx.state = lx.state(lx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lex(input string) *lexer {
|
||||
lx := &lexer{
|
||||
input: input,
|
||||
state: lexTop,
|
||||
line: 1,
|
||||
items: make(chan item, 10),
|
||||
stack: make([]stateFn, 0, 10),
|
||||
}
|
||||
return lx
|
||||
}
|
||||
|
||||
func (lx *lexer) push(state stateFn) {
|
||||
lx.stack = append(lx.stack, state)
|
||||
}
|
||||
|
||||
func (lx *lexer) pop() stateFn {
|
||||
if len(lx.stack) == 0 {
|
||||
return lx.errorf("BUG in lexer: no states to pop")
|
||||
}
|
||||
last := lx.stack[len(lx.stack)-1]
|
||||
lx.stack = lx.stack[0 : len(lx.stack)-1]
|
||||
return last
|
||||
}
|
||||
|
||||
func (lx *lexer) current() string {
|
||||
return lx.input[lx.start:lx.pos]
|
||||
}
|
||||
|
||||
func (lx *lexer) emit(typ itemType) {
|
||||
lx.items <- item{typ, lx.current(), lx.line}
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
func (lx *lexer) emitTrim(typ itemType) {
|
||||
lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
func (lx *lexer) next() (r rune) {
|
||||
if lx.atEOF {
|
||||
panic("next called after EOF")
|
||||
}
|
||||
if lx.pos >= len(lx.input) {
|
||||
lx.atEOF = true
|
||||
return eof
|
||||
}
|
||||
|
||||
if lx.input[lx.pos] == '\n' {
|
||||
lx.line++
|
||||
}
|
||||
lx.prevWidths[2] = lx.prevWidths[1]
|
||||
lx.prevWidths[1] = lx.prevWidths[0]
|
||||
if lx.nprev < 3 {
|
||||
lx.nprev++
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
|
||||
lx.prevWidths[0] = w
|
||||
lx.pos += w
|
||||
return r
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (lx *lexer) ignore() {
|
||||
lx.start = lx.pos
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can be called only twice between calls to next.
|
||||
func (lx *lexer) backup() {
|
||||
if lx.atEOF {
|
||||
lx.atEOF = false
|
||||
return
|
||||
}
|
||||
if lx.nprev < 1 {
|
||||
panic("backed up too far")
|
||||
}
|
||||
w := lx.prevWidths[0]
|
||||
lx.prevWidths[0] = lx.prevWidths[1]
|
||||
lx.prevWidths[1] = lx.prevWidths[2]
|
||||
lx.nprev--
|
||||
lx.pos -= w
|
||||
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
|
||||
lx.line--
|
||||
}
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's equal to `valid`.
|
||||
func (lx *lexer) accept(valid rune) bool {
|
||||
if lx.next() == valid {
|
||||
return true
|
||||
}
|
||||
lx.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (lx *lexer) peek() rune {
|
||||
r := lx.next()
|
||||
lx.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// skip ignores all input that matches the given predicate.
|
||||
func (lx *lexer) skip(pred func(rune) bool) {
|
||||
for {
|
||||
r := lx.next()
|
||||
if pred(r) {
|
||||
continue
|
||||
}
|
||||
lx.backup()
|
||||
lx.ignore()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// errorf stops all lexing by emitting an error and returning `nil`.
|
||||
// Note that any value that is a character is escaped if it's a special
|
||||
// character (newlines, tabs, etc.).
|
||||
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
||||
lx.items <- item{
|
||||
itemError,
|
||||
fmt.Sprintf(format, values...),
|
||||
lx.line,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lexTop consumes elements at the top level of TOML data.
|
||||
func lexTop(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isWhitespace(r) || isNL(r) {
|
||||
return lexSkip(lx, lexTop)
|
||||
}
|
||||
switch r {
|
||||
case commentStart:
|
||||
lx.push(lexTop)
|
||||
return lexCommentStart
|
||||
case tableStart:
|
||||
return lexTableStart
|
||||
case eof:
|
||||
if lx.pos > lx.start {
|
||||
return lx.errorf("unexpected EOF")
|
||||
}
|
||||
lx.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// At this point, the only valid item can be a key, so we back up
|
||||
// and let the key lexer do the rest.
|
||||
lx.backup()
|
||||
lx.push(lexTopEnd)
|
||||
return lexKeyStart
|
||||
}
|
||||
|
||||
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
|
||||
// or a table.) It must see only whitespace, and will turn back to lexTop
|
||||
// upon a newline. If it sees EOF, it will quit the lexer successfully.
|
||||
func lexTopEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == commentStart:
|
||||
// a comment will read to a newline for us.
|
||||
lx.push(lexTop)
|
||||
return lexCommentStart
|
||||
case isWhitespace(r):
|
||||
return lexTopEnd
|
||||
case isNL(r):
|
||||
lx.ignore()
|
||||
return lexTop
|
||||
case r == eof:
|
||||
lx.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
return lx.errorf("expected a top-level item to end with a newline, "+
|
||||
"comment, or EOF, but got %q instead", r)
|
||||
}
|
||||
|
||||
// lexTable lexes the beginning of a table. Namely, it makes sure that
|
||||
// it starts with a character other than '.' and ']'.
|
||||
// It assumes that '[' has already been consumed.
|
||||
// It also handles the case that this is an item in an array of tables.
|
||||
// e.g., '[[name]]'.
|
||||
func lexTableStart(lx *lexer) stateFn {
|
||||
if lx.peek() == arrayTableStart {
|
||||
lx.next()
|
||||
lx.emit(itemArrayTableStart)
|
||||
lx.push(lexArrayTableEnd)
|
||||
} else {
|
||||
lx.emit(itemTableStart)
|
||||
lx.push(lexTableEnd)
|
||||
}
|
||||
return lexTableNameStart
|
||||
}
|
||||
|
||||
func lexTableEnd(lx *lexer) stateFn {
|
||||
lx.emit(itemTableEnd)
|
||||
return lexTopEnd
|
||||
}
|
||||
|
||||
func lexArrayTableEnd(lx *lexer) stateFn {
|
||||
if r := lx.next(); r != arrayTableEnd {
|
||||
return lx.errorf("expected end of table array name delimiter %q, "+
|
||||
"but got %q instead", arrayTableEnd, r)
|
||||
}
|
||||
lx.emit(itemArrayTableEnd)
|
||||
return lexTopEnd
|
||||
}
|
||||
|
||||
func lexTableNameStart(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.peek(); {
|
||||
case r == tableEnd || r == eof:
|
||||
return lx.errorf("unexpected end of table name " +
|
||||
"(table names cannot be empty)")
|
||||
case r == tableSep:
|
||||
return lx.errorf("unexpected table separator " +
|
||||
"(table names cannot be empty)")
|
||||
case r == stringStart || r == rawStringStart:
|
||||
lx.ignore()
|
||||
lx.push(lexTableNameEnd)
|
||||
return lexValue // reuse string lexing
|
||||
default:
|
||||
return lexBareTableName
|
||||
}
|
||||
}
|
||||
|
||||
// lexBareTableName lexes the name of a table. It assumes that at least one
|
||||
// valid character for the table has already been read.
|
||||
func lexBareTableName(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isBareKeyChar(r) {
|
||||
return lexBareTableName
|
||||
}
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexTableNameEnd
|
||||
}
|
||||
|
||||
// lexTableNameEnd reads the end of a piece of a table name, optionally
|
||||
// consuming whitespace.
|
||||
func lexTableNameEnd(lx *lexer) stateFn {
|
||||
lx.skip(isWhitespace)
|
||||
switch r := lx.next(); {
|
||||
case isWhitespace(r):
|
||||
return lexTableNameEnd
|
||||
case r == tableSep:
|
||||
lx.ignore()
|
||||
return lexTableNameStart
|
||||
case r == tableEnd:
|
||||
return lx.pop()
|
||||
default:
|
||||
return lx.errorf("expected '.' or ']' to end table name, "+
|
||||
"but got %q instead", r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexKeyStart consumes a key name up until the first non-whitespace character.
|
||||
// lexKeyStart will ignore whitespace.
|
||||
func lexKeyStart(lx *lexer) stateFn {
|
||||
r := lx.peek()
|
||||
switch {
|
||||
case r == keySep:
|
||||
return lx.errorf("unexpected key separator %q", keySep)
|
||||
case isWhitespace(r) || isNL(r):
|
||||
lx.next()
|
||||
return lexSkip(lx, lexKeyStart)
|
||||
case r == stringStart || r == rawStringStart:
|
||||
lx.ignore()
|
||||
lx.emit(itemKeyStart)
|
||||
lx.push(lexKeyEnd)
|
||||
return lexValue // reuse string lexing
|
||||
default:
|
||||
lx.ignore()
|
||||
lx.emit(itemKeyStart)
|
||||
return lexBareKey
|
||||
}
|
||||
}
|
||||
|
||||
// lexBareKey consumes the text of a bare key. Assumes that the first character
|
||||
// (which is not whitespace) has not yet been consumed.
|
||||
func lexBareKey(lx *lexer) stateFn {
|
||||
switch r := lx.next(); {
|
||||
case isBareKeyChar(r):
|
||||
return lexBareKey
|
||||
case isWhitespace(r):
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexKeyEnd
|
||||
case r == keySep:
|
||||
lx.backup()
|
||||
lx.emit(itemText)
|
||||
return lexKeyEnd
|
||||
default:
|
||||
return lx.errorf("bare keys cannot contain %q", r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexKeyEnd consumes the end of a key and trims whitespace (up to the key
|
||||
// separator).
|
||||
func lexKeyEnd(lx *lexer) stateFn {
|
||||
switch r := lx.next(); {
|
||||
case r == keySep:
|
||||
return lexSkip(lx, lexValue)
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexKeyEnd)
|
||||
default:
|
||||
return lx.errorf("expected key separator %q, but got %q instead",
|
||||
keySep, r)
|
||||
}
|
||||
}
|
||||
|
||||
// lexValue starts the consumption of a value anywhere a value is expected.
|
||||
// lexValue will ignore whitespace.
|
||||
// After a value is lexed, the last state on the next is popped and returned.
|
||||
func lexValue(lx *lexer) stateFn {
|
||||
// We allow whitespace to precede a value, but NOT newlines.
|
||||
// In array syntax, the array states are responsible for ignoring newlines.
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexValue)
|
||||
case isDigit(r):
|
||||
lx.backup() // avoid an extra state and use the same as above
|
||||
return lexNumberOrDateStart
|
||||
}
|
||||
switch r {
|
||||
case arrayStart:
|
||||
lx.ignore()
|
||||
lx.emit(itemArray)
|
||||
return lexArrayValue
|
||||
case inlineTableStart:
|
||||
lx.ignore()
|
||||
lx.emit(itemInlineTableStart)
|
||||
return lexInlineTableValue
|
||||
case stringStart:
|
||||
if lx.accept(stringStart) {
|
||||
if lx.accept(stringStart) {
|
||||
lx.ignore() // Ignore """
|
||||
return lexMultilineString
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
lx.ignore() // ignore the '"'
|
||||
return lexString
|
||||
case rawStringStart:
|
||||
if lx.accept(rawStringStart) {
|
||||
if lx.accept(rawStringStart) {
|
||||
lx.ignore() // Ignore """
|
||||
return lexMultilineRawString
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
lx.ignore() // ignore the "'"
|
||||
return lexRawString
|
||||
case '+', '-':
|
||||
return lexNumberStart
|
||||
case '.': // special error case, be kind to users
|
||||
return lx.errorf("floats must start with a digit, not '.'")
|
||||
}
|
||||
if unicode.IsLetter(r) {
|
||||
// Be permissive here; lexBool will give a nice error if the
|
||||
// user wrote something like
|
||||
// x = foo
|
||||
// (i.e. not 'true' or 'false' but is something else word-like.)
|
||||
lx.backup()
|
||||
return lexBool
|
||||
}
|
||||
return lx.errorf("expected value but found %q instead", r)
|
||||
}
|
||||
|
||||
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
|
||||
// have already been consumed. All whitespace and newlines are ignored.
|
||||
func lexArrayValue(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r) || isNL(r):
|
||||
return lexSkip(lx, lexArrayValue)
|
||||
case r == commentStart:
|
||||
lx.push(lexArrayValue)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
return lx.errorf("unexpected comma")
|
||||
case r == arrayEnd:
|
||||
// NOTE(caleb): The spec isn't clear about whether you can have
|
||||
// a trailing comma or not, so we'll allow it.
|
||||
return lexArrayEnd
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.push(lexArrayValueEnd)
|
||||
return lexValue
|
||||
}
|
||||
|
||||
// lexArrayValueEnd consumes everything between the end of an array value and
|
||||
// the next value (or the end of the array): it ignores whitespace and newlines
|
||||
// and expects either a ',' or a ']'.
|
||||
func lexArrayValueEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r) || isNL(r):
|
||||
return lexSkip(lx, lexArrayValueEnd)
|
||||
case r == commentStart:
|
||||
lx.push(lexArrayValueEnd)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
lx.ignore()
|
||||
return lexArrayValue // move on to the next value
|
||||
case r == arrayEnd:
|
||||
return lexArrayEnd
|
||||
}
|
||||
return lx.errorf(
|
||||
"expected a comma or array terminator %q, but got %q instead",
|
||||
arrayEnd, r,
|
||||
)
|
||||
}
|
||||
|
||||
// lexArrayEnd finishes the lexing of an array.
|
||||
// It assumes that a ']' has just been consumed.
|
||||
func lexArrayEnd(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.emit(itemArrayEnd)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexInlineTableValue consumes one key/value pair in an inline table.
|
||||
// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
|
||||
func lexInlineTableValue(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexInlineTableValue)
|
||||
case isNL(r):
|
||||
return lx.errorf("newlines not allowed within inline tables")
|
||||
case r == commentStart:
|
||||
lx.push(lexInlineTableValue)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
return lx.errorf("unexpected comma")
|
||||
case r == inlineTableEnd:
|
||||
return lexInlineTableEnd
|
||||
}
|
||||
lx.backup()
|
||||
lx.push(lexInlineTableValueEnd)
|
||||
return lexKeyStart
|
||||
}
|
||||
|
||||
// lexInlineTableValueEnd consumes everything between the end of an inline table
|
||||
// key/value pair and the next pair (or the end of the table):
|
||||
// it ignores whitespace and expects either a ',' or a '}'.
|
||||
func lexInlineTableValueEnd(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case isWhitespace(r):
|
||||
return lexSkip(lx, lexInlineTableValueEnd)
|
||||
case isNL(r):
|
||||
return lx.errorf("newlines not allowed within inline tables")
|
||||
case r == commentStart:
|
||||
lx.push(lexInlineTableValueEnd)
|
||||
return lexCommentStart
|
||||
case r == comma:
|
||||
lx.ignore()
|
||||
return lexInlineTableValue
|
||||
case r == inlineTableEnd:
|
||||
return lexInlineTableEnd
|
||||
}
|
||||
return lx.errorf("expected a comma or an inline table terminator %q, "+
|
||||
"but got %q instead", inlineTableEnd, r)
|
||||
}
|
||||
|
||||
// lexInlineTableEnd finishes the lexing of an inline table.
|
||||
// It assumes that a '}' has just been consumed.
|
||||
func lexInlineTableEnd(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.emit(itemInlineTableEnd)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexString consumes the inner contents of a string. It assumes that the
|
||||
// beginning '"' has already been consumed and ignored.
|
||||
func lexString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == eof:
|
||||
return lx.errorf("unexpected EOF")
|
||||
case isNL(r):
|
||||
return lx.errorf("strings cannot contain newlines")
|
||||
case r == '\\':
|
||||
lx.push(lexString)
|
||||
return lexStringEscape
|
||||
case r == stringEnd:
|
||||
lx.backup()
|
||||
lx.emit(itemString)
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
return lexString
|
||||
}
|
||||
|
||||
// lexMultilineString consumes the inner contents of a string. It assumes that
|
||||
// the beginning '"""' has already been consumed and ignored.
|
||||
func lexMultilineString(lx *lexer) stateFn {
|
||||
switch lx.next() {
|
||||
case eof:
|
||||
return lx.errorf("unexpected EOF")
|
||||
case '\\':
|
||||
return lexMultilineStringEscape
|
||||
case stringEnd:
|
||||
if lx.accept(stringEnd) {
|
||||
if lx.accept(stringEnd) {
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.emit(itemMultilineString)
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
}
|
||||
return lexMultilineString
|
||||
}
|
||||
|
||||
// lexRawString consumes a raw string. Nothing can be escaped in such a string.
|
||||
// It assumes that the beginning "'" has already been consumed and ignored.
|
||||
func lexRawString(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch {
|
||||
case r == eof:
|
||||
return lx.errorf("unexpected EOF")
|
||||
case isNL(r):
|
||||
return lx.errorf("strings cannot contain newlines")
|
||||
case r == rawStringEnd:
|
||||
lx.backup()
|
||||
lx.emit(itemRawString)
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
return lexRawString
|
||||
}
|
||||
|
||||
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
||||
// a string. It assumes that the beginning "'''" has already been consumed and
|
||||
// ignored.
|
||||
func lexMultilineRawString(lx *lexer) stateFn {
|
||||
switch lx.next() {
|
||||
case eof:
|
||||
return lx.errorf("unexpected EOF")
|
||||
case rawStringEnd:
|
||||
if lx.accept(rawStringEnd) {
|
||||
if lx.accept(rawStringEnd) {
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.backup()
|
||||
lx.emit(itemRawMultilineString)
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.next()
|
||||
lx.ignore()
|
||||
return lx.pop()
|
||||
}
|
||||
lx.backup()
|
||||
}
|
||||
}
|
||||
return lexMultilineRawString
|
||||
}
|
||||
|
||||
// lexMultilineStringEscape consumes an escaped character. It assumes that the
|
||||
// preceding '\\' has already been consumed.
|
||||
func lexMultilineStringEscape(lx *lexer) stateFn {
|
||||
// Handle the special case first:
|
||||
if isNL(lx.next()) {
|
||||
return lexMultilineString
|
||||
}
|
||||
lx.backup()
|
||||
lx.push(lexMultilineString)
|
||||
return lexStringEscape(lx)
|
||||
}
|
||||
|
||||
func lexStringEscape(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
switch r {
|
||||
case 'b':
|
||||
fallthrough
|
||||
case 't':
|
||||
fallthrough
|
||||
case 'n':
|
||||
fallthrough
|
||||
case 'f':
|
||||
fallthrough
|
||||
case 'r':
|
||||
fallthrough
|
||||
case '"':
|
||||
fallthrough
|
||||
case '\\':
|
||||
return lx.pop()
|
||||
case 'u':
|
||||
return lexShortUnicodeEscape
|
||||
case 'U':
|
||||
return lexLongUnicodeEscape
|
||||
}
|
||||
return lx.errorf("invalid escape character %q; only the following "+
|
||||
"escape characters are allowed: "+
|
||||
`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
|
||||
}
|
||||
|
||||
func lexShortUnicodeEscape(lx *lexer) stateFn {
|
||||
var r rune
|
||||
for i := 0; i < 4; i++ {
|
||||
r = lx.next()
|
||||
if !isHexadecimal(r) {
|
||||
return lx.errorf(`expected four hexadecimal digits after '\u', `+
|
||||
"but got %q instead", lx.current())
|
||||
}
|
||||
}
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
func lexLongUnicodeEscape(lx *lexer) stateFn {
|
||||
var r rune
|
||||
for i := 0; i < 8; i++ {
|
||||
r = lx.next()
|
||||
if !isHexadecimal(r) {
|
||||
return lx.errorf(`expected eight hexadecimal digits after '\U', `+
|
||||
"but got %q instead", lx.current())
|
||||
}
|
||||
}
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexNumberOrDateStart consumes either an integer, a float, or datetime.
|
||||
func lexNumberOrDateStart(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumberOrDate
|
||||
}
|
||||
switch r {
|
||||
case '_':
|
||||
return lexNumber
|
||||
case 'e', 'E':
|
||||
return lexFloat
|
||||
case '.':
|
||||
return lx.errorf("floats must start with a digit, not '.'")
|
||||
}
|
||||
return lx.errorf("expected a digit but got %q", r)
|
||||
}
|
||||
|
||||
// lexNumberOrDate consumes either an integer, float or datetime.
|
||||
func lexNumberOrDate(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumberOrDate
|
||||
}
|
||||
switch r {
|
||||
case '-':
|
||||
return lexDatetime
|
||||
case '_':
|
||||
return lexNumber
|
||||
case '.', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemInteger)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexDatetime consumes a Datetime, to a first approximation.
|
||||
// The parser validates that it matches one of the accepted formats.
|
||||
func lexDatetime(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexDatetime
|
||||
}
|
||||
switch r {
|
||||
case '-', 'T', ':', '.', 'Z', '+':
|
||||
return lexDatetime
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemDatetime)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexNumberStart consumes either an integer or a float. It assumes that a sign
|
||||
// has already been read, but that *no* digits have been consumed.
|
||||
// lexNumberStart will move to the appropriate integer or float states.
|
||||
func lexNumberStart(lx *lexer) stateFn {
|
||||
// We MUST see a digit. Even floats have to start with a digit.
|
||||
r := lx.next()
|
||||
if !isDigit(r) {
|
||||
if r == '.' {
|
||||
return lx.errorf("floats must start with a digit, not '.'")
|
||||
}
|
||||
return lx.errorf("expected a digit but got %q", r)
|
||||
}
|
||||
return lexNumber
|
||||
}
|
||||
|
||||
// lexNumber consumes an integer or a float after seeing the first digit.
|
||||
func lexNumber(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexNumber
|
||||
}
|
||||
switch r {
|
||||
case '_':
|
||||
return lexNumber
|
||||
case '.', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemInteger)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexFloat consumes the elements of a float. It allows any sequence of
|
||||
// float-like characters, so floats emitted by the lexer are only a first
|
||||
// approximation and must be validated by the parser.
|
||||
func lexFloat(lx *lexer) stateFn {
|
||||
r := lx.next()
|
||||
if isDigit(r) {
|
||||
return lexFloat
|
||||
}
|
||||
switch r {
|
||||
case '_', '.', '-', '+', 'e', 'E':
|
||||
return lexFloat
|
||||
}
|
||||
|
||||
lx.backup()
|
||||
lx.emit(itemFloat)
|
||||
return lx.pop()
|
||||
}
|
||||
|
||||
// lexBool consumes a bool string: 'true' or 'false.
|
||||
func lexBool(lx *lexer) stateFn {
|
||||
var rs []rune
|
||||
for {
|
||||
r := lx.next()
|
||||
if !unicode.IsLetter(r) {
|
||||
lx.backup()
|
||||
break
|
||||
}
|
||||
rs = append(rs, r)
|
||||
}
|
||||
s := string(rs)
|
||||
switch s {
|
||||
case "true", "false":
|
||||
lx.emit(itemBool)
|
||||
return lx.pop()
|
||||
}
|
||||
return lx.errorf("expected value but found %q instead", s)
|
||||
}
|
||||
|
||||
// lexCommentStart begins the lexing of a comment. It will emit
|
||||
// itemCommentStart and consume no characters, passing control to lexComment.
|
||||
func lexCommentStart(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
lx.emit(itemCommentStart)
|
||||
return lexComment
|
||||
}
|
||||
|
||||
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
|
||||
// It will consume *up to* the first newline character, and pass control
|
||||
// back to the last state on the stack.
|
||||
func lexComment(lx *lexer) stateFn {
|
||||
r := lx.peek()
|
||||
if isNL(r) || r == eof {
|
||||
lx.emit(itemText)
|
||||
return lx.pop()
|
||||
}
|
||||
lx.next()
|
||||
return lexComment
|
||||
}
|
||||
|
||||
// lexSkip ignores all slurped input and moves on to the next state.
|
||||
func lexSkip(lx *lexer, nextState stateFn) stateFn {
|
||||
return func(lx *lexer) stateFn {
|
||||
lx.ignore()
|
||||
return nextState
|
||||
}
|
||||
}
|
||||
|
||||
// isWhitespace returns true if `r` is a whitespace character according
|
||||
// to the spec.
|
||||
func isWhitespace(r rune) bool {
|
||||
return r == '\t' || r == ' '
|
||||
}
|
||||
|
||||
func isNL(r rune) bool {
|
||||
return r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
func isDigit(r rune) bool {
|
||||
return r >= '0' && r <= '9'
|
||||
}
|
||||
|
||||
func isHexadecimal(r rune) bool {
|
||||
return (r >= '0' && r <= '9') ||
|
||||
(r >= 'a' && r <= 'f') ||
|
||||
(r >= 'A' && r <= 'F')
|
||||
}
|
||||
|
||||
func isBareKeyChar(r rune) bool {
|
||||
return (r >= 'A' && r <= 'Z') ||
|
||||
(r >= 'a' && r <= 'z') ||
|
||||
(r >= '0' && r <= '9') ||
|
||||
r == '_' ||
|
||||
r == '-'
|
||||
}
|
||||
|
||||
func (itype itemType) String() string {
|
||||
switch itype {
|
||||
case itemError:
|
||||
return "Error"
|
||||
case itemNIL:
|
||||
return "NIL"
|
||||
case itemEOF:
|
||||
return "EOF"
|
||||
case itemText:
|
||||
return "Text"
|
||||
case itemString, itemRawString, itemMultilineString, itemRawMultilineString:
|
||||
return "String"
|
||||
case itemBool:
|
||||
return "Bool"
|
||||
case itemInteger:
|
||||
return "Integer"
|
||||
case itemFloat:
|
||||
return "Float"
|
||||
case itemDatetime:
|
||||
return "DateTime"
|
||||
case itemTableStart:
|
||||
return "TableStart"
|
||||
case itemTableEnd:
|
||||
return "TableEnd"
|
||||
case itemKeyStart:
|
||||
return "KeyStart"
|
||||
case itemArray:
|
||||
return "Array"
|
||||
case itemArrayEnd:
|
||||
return "ArrayEnd"
|
||||
case itemCommentStart:
|
||||
return "CommentStart"
|
||||
}
|
||||
panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
|
||||
}
|
||||
|
||||
func (item item) String() string {
|
||||
return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
|
||||
}
|
592
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
592
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
@ -0,0 +1,592 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
mapping map[string]interface{}
|
||||
types map[string]tomlType
|
||||
lx *lexer
|
||||
|
||||
// A list of keys in the order that they appear in the TOML data.
|
||||
ordered []Key
|
||||
|
||||
// the full key for the current hash in scope
|
||||
context Key
|
||||
|
||||
// the base key name for everything except hashes
|
||||
currentKey string
|
||||
|
||||
// rough approximation of line number
|
||||
approxLine int
|
||||
|
||||
// A map of 'key.group.names' to whether they were created implicitly.
|
||||
implicits map[string]bool
|
||||
}
|
||||
|
||||
type parseError string
|
||||
|
||||
func (pe parseError) Error() string {
|
||||
return string(pe)
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
if err, ok = r.(parseError); ok {
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
p = &parser{
|
||||
mapping: make(map[string]interface{}),
|
||||
types: make(map[string]tomlType),
|
||||
lx: lex(data),
|
||||
ordered: make([]Key, 0),
|
||||
implicits: make(map[string]bool),
|
||||
}
|
||||
for {
|
||||
item := p.next()
|
||||
if item.typ == itemEOF {
|
||||
break
|
||||
}
|
||||
p.topLevel(item)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *parser) panicf(format string, v ...interface{}) {
|
||||
msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
|
||||
p.approxLine, p.current(), fmt.Sprintf(format, v...))
|
||||
panic(parseError(msg))
|
||||
}
|
||||
|
||||
func (p *parser) next() item {
|
||||
it := p.lx.nextItem()
|
||||
if it.typ == itemError {
|
||||
p.panicf("%s", it.val)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) bug(format string, v ...interface{}) {
|
||||
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||
}
|
||||
|
||||
func (p *parser) expect(typ itemType) item {
|
||||
it := p.next()
|
||||
p.assertEqual(typ, it.typ)
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) assertEqual(expected, got itemType) {
|
||||
if expected != got {
|
||||
p.bug("Expected '%s' but got '%s'.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) topLevel(item item) {
|
||||
switch item.typ {
|
||||
case itemCommentStart:
|
||||
p.approxLine = item.line
|
||||
p.expect(itemText)
|
||||
case itemTableStart:
|
||||
kg := p.next()
|
||||
p.approxLine = kg.line
|
||||
|
||||
var key Key
|
||||
for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||
key = append(key, p.keyString(kg))
|
||||
}
|
||||
p.assertEqual(itemTableEnd, kg.typ)
|
||||
|
||||
p.establishContext(key, false)
|
||||
p.setType("", tomlHash)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemArrayTableStart:
|
||||
kg := p.next()
|
||||
p.approxLine = kg.line
|
||||
|
||||
var key Key
|
||||
for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() {
|
||||
key = append(key, p.keyString(kg))
|
||||
}
|
||||
p.assertEqual(itemArrayTableEnd, kg.typ)
|
||||
|
||||
p.establishContext(key, true)
|
||||
p.setType("", tomlArrayHash)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemKeyStart:
|
||||
kname := p.next()
|
||||
p.approxLine = kname.line
|
||||
p.currentKey = p.keyString(kname)
|
||||
|
||||
val, typ := p.value(p.next())
|
||||
p.setValue(p.currentKey, val)
|
||||
p.setType(p.currentKey, typ)
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
p.currentKey = ""
|
||||
default:
|
||||
p.bug("Unexpected type at top level: %s", item.typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Gets a string for a key (or part of a key in a table name).
|
||||
func (p *parser) keyString(it item) string {
|
||||
switch it.typ {
|
||||
case itemText:
|
||||
return it.val
|
||||
case itemString, itemMultilineString,
|
||||
itemRawString, itemRawMultilineString:
|
||||
s, _ := p.value(it)
|
||||
return s.(string)
|
||||
default:
|
||||
p.bug("Unexpected key type: %s", it.typ)
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// value translates an expected value from the lexer into a Go value wrapped
|
||||
// as an empty interface.
|
||||
func (p *parser) value(it item) (interface{}, tomlType) {
|
||||
switch it.typ {
|
||||
case itemString:
|
||||
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
|
||||
case itemMultilineString:
|
||||
trimmed := stripFirstNewline(stripEscapedWhitespace(it.val))
|
||||
return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
|
||||
case itemRawString:
|
||||
return it.val, p.typeOfPrimitive(it)
|
||||
case itemRawMultilineString:
|
||||
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
||||
case itemBool:
|
||||
switch it.val {
|
||||
case "true":
|
||||
return true, p.typeOfPrimitive(it)
|
||||
case "false":
|
||||
return false, p.typeOfPrimitive(it)
|
||||
}
|
||||
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||
case itemInteger:
|
||||
if !numUnderscoresOK(it.val) {
|
||||
p.panicf("Invalid integer %q: underscores must be surrounded by digits",
|
||||
it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
num, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||
// provides an invalid integer, but it's possible that the number is
|
||||
// out of range of valid values (which the lexer cannot determine).
|
||||
// So mark the former as a bug but the latter as a legitimate user
|
||||
// error.
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
|
||||
p.panicf("Integer '%s' is out of the range of 64-bit "+
|
||||
"signed integers.", it.val)
|
||||
} else {
|
||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
case itemFloat:
|
||||
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||
switch r {
|
||||
case '.', 'e', 'E':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, part := range parts {
|
||||
if !numUnderscoresOK(part) {
|
||||
p.panicf("Invalid float %q: underscores must be "+
|
||||
"surrounded by digits", it.val)
|
||||
}
|
||||
}
|
||||
if !numPeriodsOK(it.val) {
|
||||
// As a special case, numbers like '123.' or '1.e2',
|
||||
// which are valid as far as Go/strconv are concerned,
|
||||
// must be rejected because TOML says that a fractional
|
||||
// part consists of '.' followed by 1+ digits.
|
||||
p.panicf("Invalid float %q: '.' must be followed "+
|
||||
"by one or more digits", it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
num, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok &&
|
||||
e.Err == strconv.ErrRange {
|
||||
|
||||
p.panicf("Float '%s' is out of the range of 64-bit "+
|
||||
"IEEE-754 floating-point numbers.", it.val)
|
||||
} else {
|
||||
p.panicf("Invalid float value: %q", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
case itemDatetime:
|
||||
var t time.Time
|
||||
var ok bool
|
||||
var err error
|
||||
for _, format := range []string{
|
||||
"2006-01-02T15:04:05Z07:00",
|
||||
"2006-01-02T15:04:05",
|
||||
"2006-01-02",
|
||||
} {
|
||||
t, err = time.ParseInLocation(format, it.val, time.Local)
|
||||
if err == nil {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.panicf("Invalid TOML Datetime: %q.", it.val)
|
||||
}
|
||||
return t, p.typeOfPrimitive(it)
|
||||
case itemArray:
|
||||
array := make([]interface{}, 0)
|
||||
types := make([]tomlType, 0)
|
||||
|
||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
val, typ := p.value(it)
|
||||
array = append(array, val)
|
||||
types = append(types, typ)
|
||||
}
|
||||
return array, p.typeOfArray(types)
|
||||
case itemInlineTableStart:
|
||||
var (
|
||||
hash = make(map[string]interface{})
|
||||
outerContext = p.context
|
||||
outerKey = p.currentKey
|
||||
)
|
||||
|
||||
p.context = append(p.context, p.currentKey)
|
||||
p.currentKey = ""
|
||||
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
||||
if it.typ != itemKeyStart {
|
||||
p.bug("Expected key start but instead found %q, around line %d",
|
||||
it.val, p.approxLine)
|
||||
}
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
// retrieve key
|
||||
k := p.next()
|
||||
p.approxLine = k.line
|
||||
kname := p.keyString(k)
|
||||
|
||||
// retrieve value
|
||||
p.currentKey = kname
|
||||
val, typ := p.value(p.next())
|
||||
// make sure we keep metadata up to date
|
||||
p.setType(kname, typ)
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
hash[kname] = val
|
||||
}
|
||||
p.context = outerContext
|
||||
p.currentKey = outerKey
|
||||
return hash, tomlHash
|
||||
}
|
||||
p.bug("Unexpected value type: %s", it.typ)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
||||
// characters that are not underscores.
|
||||
func numUnderscoresOK(s string) bool {
|
||||
accept := false
|
||||
for _, r := range s {
|
||||
if r == '_' {
|
||||
if !accept {
|
||||
return false
|
||||
}
|
||||
accept = false
|
||||
continue
|
||||
}
|
||||
accept = true
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
// numPeriodsOK checks whether every period in s is followed by a digit.
|
||||
func numPeriodsOK(s string) bool {
|
||||
period := false
|
||||
for _, r := range s {
|
||||
if period && !isDigit(r) {
|
||||
return false
|
||||
}
|
||||
period = r == '.'
|
||||
}
|
||||
return !period
|
||||
}
|
||||
|
||||
// establishContext sets the current context of the parser,
|
||||
// where the context is either a hash or an array of hashes. Which one is
|
||||
// set depends on the value of the `array` parameter.
|
||||
//
|
||||
// Establishing the context also makes sure that the key isn't a duplicate, and
|
||||
// will create implicit hashes automatically.
|
||||
func (p *parser) establishContext(key Key, array bool) {
|
||||
var ok bool
|
||||
|
||||
// Always start at the top level and drill down for our context.
|
||||
hashContext := p.mapping
|
||||
keyContext := make(Key, 0)
|
||||
|
||||
// We only need implicit hashes for key[0:-1]
|
||||
for _, k := range key[0 : len(key)-1] {
|
||||
_, ok = hashContext[k]
|
||||
keyContext = append(keyContext, k)
|
||||
|
||||
// No key? Make an implicit hash and move on.
|
||||
if !ok {
|
||||
p.addImplicit(keyContext)
|
||||
hashContext[k] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// If the hash context is actually an array of tables, then set
|
||||
// the hash context to the last element in that array.
|
||||
//
|
||||
// Otherwise, it better be a table, since this MUST be a key group (by
|
||||
// virtue of it not being the last element in a key).
|
||||
switch t := hashContext[k].(type) {
|
||||
case []map[string]interface{}:
|
||||
hashContext = t[len(t)-1]
|
||||
case map[string]interface{}:
|
||||
hashContext = t
|
||||
default:
|
||||
p.panicf("Key '%s' was already created as a hash.", keyContext)
|
||||
}
|
||||
}
|
||||
|
||||
p.context = keyContext
|
||||
if array {
|
||||
// If this is the first element for this array, then allocate a new
|
||||
// list of tables for it.
|
||||
k := key[len(key)-1]
|
||||
if _, ok := hashContext[k]; !ok {
|
||||
hashContext[k] = make([]map[string]interface{}, 0, 5)
|
||||
}
|
||||
|
||||
// Add a new table. But make sure the key hasn't already been used
|
||||
// for something else.
|
||||
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
||||
hashContext[k] = append(hash, make(map[string]interface{}))
|
||||
} else {
|
||||
p.panicf("Key '%s' was already created and cannot be used as "+
|
||||
"an array.", keyContext)
|
||||
}
|
||||
} else {
|
||||
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
||||
}
|
||||
p.context = append(p.context, key[len(key)-1])
|
||||
}
|
||||
|
||||
// setValue sets the given key to the given value in the current context.
|
||||
// It will make sure that the key hasn't already been defined, account for
|
||||
// implicit key groups.
|
||||
func (p *parser) setValue(key string, value interface{}) {
|
||||
var tmpHash interface{}
|
||||
var ok bool
|
||||
|
||||
hash := p.mapping
|
||||
keyContext := make(Key, 0)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
if tmpHash, ok = hash[k]; !ok {
|
||||
p.bug("Context for key '%s' has not been established.", keyContext)
|
||||
}
|
||||
switch t := tmpHash.(type) {
|
||||
case []map[string]interface{}:
|
||||
// The context is a table of hashes. Pick the most recent table
|
||||
// defined as the current hash.
|
||||
hash = t[len(t)-1]
|
||||
case map[string]interface{}:
|
||||
hash = t
|
||||
default:
|
||||
p.bug("Expected hash to have type 'map[string]interface{}', but "+
|
||||
"it has '%T' instead.", tmpHash)
|
||||
}
|
||||
}
|
||||
keyContext = append(keyContext, key)
|
||||
|
||||
if _, ok := hash[key]; ok {
|
||||
// Typically, if the given key has already been set, then we have
|
||||
// to raise an error since duplicate keys are disallowed. However,
|
||||
// it's possible that a key was previously defined implicitly. In this
|
||||
// case, it is allowed to be redefined concretely. (See the
|
||||
// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
|
||||
//
|
||||
// But we have to make sure to stop marking it as an implicit. (So that
|
||||
// another redefinition provokes an error.)
|
||||
//
|
||||
// Note that since it has already been defined (as a hash), we don't
|
||||
// want to overwrite it. So our business is done.
|
||||
if p.isImplicit(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we have a concrete key trying to override a previous
|
||||
// key, which is *always* wrong.
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
hash[key] = value
|
||||
}
|
||||
|
||||
// setType sets the type of a particular value at a given key.
|
||||
// It should be called immediately AFTER setValue.
|
||||
//
|
||||
// Note that if `key` is empty, then the type given will be applied to the
|
||||
// current context (which is either a table or an array of tables).
|
||||
func (p *parser) setType(key string, typ tomlType) {
|
||||
keyContext := make(Key, 0, len(p.context)+1)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
}
|
||||
if len(key) > 0 { // allow type setting for hashes
|
||||
keyContext = append(keyContext, key)
|
||||
}
|
||||
p.types[keyContext.String()] = typ
|
||||
}
|
||||
|
||||
// addImplicit sets the given Key as having been created implicitly.
|
||||
func (p *parser) addImplicit(key Key) {
|
||||
p.implicits[key.String()] = true
|
||||
}
|
||||
|
||||
// removeImplicit stops tagging the given key as having been implicitly
|
||||
// created.
|
||||
func (p *parser) removeImplicit(key Key) {
|
||||
p.implicits[key.String()] = false
|
||||
}
|
||||
|
||||
// isImplicit returns true if the key group pointed to by the key was created
|
||||
// implicitly.
|
||||
func (p *parser) isImplicit(key Key) bool {
|
||||
return p.implicits[key.String()]
|
||||
}
|
||||
|
||||
// current returns the full key name of the current context.
|
||||
func (p *parser) current() string {
|
||||
if len(p.currentKey) == 0 {
|
||||
return p.context.String()
|
||||
}
|
||||
if len(p.context) == 0 {
|
||||
return p.currentKey
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
|
||||
}
|
||||
|
||||
func stripFirstNewline(s string) string {
|
||||
if len(s) == 0 || s[0] != '\n' {
|
||||
return s
|
||||
}
|
||||
return s[1:]
|
||||
}
|
||||
|
||||
func stripEscapedWhitespace(s string) string {
|
||||
esc := strings.Split(s, "\\\n")
|
||||
if len(esc) > 1 {
|
||||
for i := 1; i < len(esc); i++ {
|
||||
esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace)
|
||||
}
|
||||
}
|
||||
return strings.Join(esc, "")
|
||||
}
|
||||
|
||||
func (p *parser) replaceEscapes(str string) string {
|
||||
var replaced []rune
|
||||
s := []byte(str)
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
if s[r] != '\\' {
|
||||
c, size := utf8.DecodeRune(s[r:])
|
||||
r += size
|
||||
replaced = append(replaced, c)
|
||||
continue
|
||||
}
|
||||
r += 1
|
||||
if r >= len(s) {
|
||||
p.bug("Escape sequence at end of string.")
|
||||
return ""
|
||||
}
|
||||
switch s[r] {
|
||||
default:
|
||||
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
||||
return ""
|
||||
case 'b':
|
||||
replaced = append(replaced, rune(0x0008))
|
||||
r += 1
|
||||
case 't':
|
||||
replaced = append(replaced, rune(0x0009))
|
||||
r += 1
|
||||
case 'n':
|
||||
replaced = append(replaced, rune(0x000A))
|
||||
r += 1
|
||||
case 'f':
|
||||
replaced = append(replaced, rune(0x000C))
|
||||
r += 1
|
||||
case 'r':
|
||||
replaced = append(replaced, rune(0x000D))
|
||||
r += 1
|
||||
case '"':
|
||||
replaced = append(replaced, rune(0x0022))
|
||||
r += 1
|
||||
case '\\':
|
||||
replaced = append(replaced, rune(0x005C))
|
||||
r += 1
|
||||
case 'u':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 5
|
||||
case 'U':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 9
|
||||
}
|
||||
}
|
||||
return string(replaced)
|
||||
}
|
||||
|
||||
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
||||
s := string(bs)
|
||||
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||
if err != nil {
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
||||
"lexer claims it's OK: %s", s, err)
|
||||
}
|
||||
if !utf8.ValidRune(rune(hex)) {
|
||||
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
}
|
||||
return rune(hex)
|
||||
}
|
||||
|
||||
func isStringType(ty itemType) bool {
|
||||
return ty == itemString || ty == itemMultilineString ||
|
||||
ty == itemRawString || ty == itemRawMultilineString
|
||||
}
|
91
vendor/github.com/BurntSushi/toml/type_check.go
generated
vendored
Normal file
91
vendor/github.com/BurntSushi/toml/type_check.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
package toml
|
||||
|
||||
// tomlType represents any Go type that corresponds to a TOML type.
|
||||
// While the first draft of the TOML spec has a simplistic type system that
|
||||
// probably doesn't need this level of sophistication, we seem to be militating
|
||||
// toward adding real composite types.
|
||||
type tomlType interface {
|
||||
typeString() string
|
||||
}
|
||||
|
||||
// typeEqual accepts any two types and returns true if they are equal.
|
||||
func typeEqual(t1, t2 tomlType) bool {
|
||||
if t1 == nil || t2 == nil {
|
||||
return false
|
||||
}
|
||||
return t1.typeString() == t2.typeString()
|
||||
}
|
||||
|
||||
func typeIsHash(t tomlType) bool {
|
||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||
}
|
||||
|
||||
type tomlBaseType string
|
||||
|
||||
func (btype tomlBaseType) typeString() string {
|
||||
return string(btype)
|
||||
}
|
||||
|
||||
func (btype tomlBaseType) String() string {
|
||||
return btype.typeString()
|
||||
}
|
||||
|
||||
var (
|
||||
tomlInteger tomlBaseType = "Integer"
|
||||
tomlFloat tomlBaseType = "Float"
|
||||
tomlDatetime tomlBaseType = "Datetime"
|
||||
tomlString tomlBaseType = "String"
|
||||
tomlBool tomlBaseType = "Bool"
|
||||
tomlArray tomlBaseType = "Array"
|
||||
tomlHash tomlBaseType = "Hash"
|
||||
tomlArrayHash tomlBaseType = "ArrayHash"
|
||||
)
|
||||
|
||||
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
|
||||
// Primitive values are: Integer, Float, Datetime, String and Bool.
|
||||
//
|
||||
// Passing a lexer item other than the following will cause a BUG message
|
||||
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
|
||||
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
||||
switch lexItem.typ {
|
||||
case itemInteger:
|
||||
return tomlInteger
|
||||
case itemFloat:
|
||||
return tomlFloat
|
||||
case itemDatetime:
|
||||
return tomlDatetime
|
||||
case itemString:
|
||||
return tomlString
|
||||
case itemMultilineString:
|
||||
return tomlString
|
||||
case itemRawString:
|
||||
return tomlString
|
||||
case itemRawMultilineString:
|
||||
return tomlString
|
||||
case itemBool:
|
||||
return tomlBool
|
||||
}
|
||||
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// typeOfArray returns a tomlType for an array given a list of types of its
|
||||
// values.
|
||||
//
|
||||
// In the current spec, if an array is homogeneous, then its type is always
|
||||
// "Array". If the array is not homogeneous, an error is generated.
|
||||
func (p *parser) typeOfArray(types []tomlType) tomlType {
|
||||
// Empty arrays are cool.
|
||||
if len(types) == 0 {
|
||||
return tomlArray
|
||||
}
|
||||
|
||||
theType := types[0]
|
||||
for _, t := range types[1:] {
|
||||
if !typeEqual(theType, t) {
|
||||
p.panicf("Array contains values of type '%s' and '%s', but "+
|
||||
"arrays must be homogeneous.", theType, t)
|
||||
}
|
||||
}
|
||||
return tomlArray
|
||||
}
|
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
||||
package toml
|
||||
|
||||
// Struct field handling is adapted from code in encoding/json:
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the Go distribution.
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A field represents a single field found in a struct.
|
||||
type field struct {
|
||||
name string // the name of the field (`toml` tag included)
|
||||
tag bool // whether field has a `toml` tag
|
||||
index []int // represents the depth of an anonymous field
|
||||
typ reflect.Type // the type of the field
|
||||
}
|
||||
|
||||
// byName sorts field by name, breaking ties with depth,
|
||||
// then breaking ties with "name came from toml tag", then
|
||||
// breaking ties with index sequence.
|
||||
type byName []field
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
|
||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byName) Less(i, j int) bool {
|
||||
if x[i].name != x[j].name {
|
||||
return x[i].name < x[j].name
|
||||
}
|
||||
if len(x[i].index) != len(x[j].index) {
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
if x[i].tag != x[j].tag {
|
||||
return x[i].tag
|
||||
}
|
||||
return byIndex(x).Less(i, j)
|
||||
}
|
||||
|
||||
// byIndex sorts field by index sequence.
|
||||
type byIndex []field
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
|
||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byIndex) Less(i, j int) bool {
|
||||
for k, xik := range x[i].index {
|
||||
if k >= len(x[j].index) {
|
||||
return false
|
||||
}
|
||||
if xik != x[j].index[k] {
|
||||
return xik < x[j].index[k]
|
||||
}
|
||||
}
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
|
||||
// typeFields returns a list of fields that TOML should recognize for the given
|
||||
// type. The algorithm is breadth-first search over the set of structs to
|
||||
// include - the top struct and then any reachable anonymous structs.
|
||||
func typeFields(t reflect.Type) []field {
|
||||
// Anonymous fields to explore at the current level and the next.
|
||||
current := []field{}
|
||||
next := []field{{typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
count := map[reflect.Type]int{}
|
||||
nextCount := map[reflect.Type]int{}
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
// Fields found.
|
||||
var fields []field
|
||||
|
||||
for len(next) > 0 {
|
||||
current, next = next, current[:0]
|
||||
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||
|
||||
for _, f := range current {
|
||||
if visited[f.typ] {
|
||||
continue
|
||||
}
|
||||
visited[f.typ] = true
|
||||
|
||||
// Scan f.typ for fields to include.
|
||||
for i := 0; i < f.typ.NumField(); i++ {
|
||||
sf := f.typ.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
continue
|
||||
}
|
||||
opts := getOptions(sf.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
index := make([]int, len(f.index)+1)
|
||||
copy(index, f.index)
|
||||
index[len(f.index)] = i
|
||||
|
||||
ft := sf.Type
|
||||
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||
// Follow pointer.
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
// Record found field and index sequence.
|
||||
if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||
tagged := opts.name != ""
|
||||
name := opts.name
|
||||
if name == "" {
|
||||
name = sf.Name
|
||||
}
|
||||
fields = append(fields, field{name, tagged, index, ft})
|
||||
if count[f.typ] > 1 {
|
||||
// If there were multiple instances, add a second,
|
||||
// so that the annihilation code will see a duplicate.
|
||||
// It only cares about the distinction between 1 or 2,
|
||||
// so don't bother generating any more copies.
|
||||
fields = append(fields, fields[len(fields)-1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Record new anonymous struct to explore in next round.
|
||||
nextCount[ft]++
|
||||
if nextCount[ft] == 1 {
|
||||
f := field{name: ft.Name(), index: index, typ: ft}
|
||||
next = append(next, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byName(fields))
|
||||
|
||||
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||
// except that fields with TOML tags are promoted.
|
||||
|
||||
// The fields are sorted in primary order of name, secondary order
|
||||
// of field index length. Loop over names; for each name, delete
|
||||
// hidden fields by choosing the one dominant field that survives.
|
||||
out := fields[:0]
|
||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||
// One iteration per name.
|
||||
// Find the sequence of fields with the name of this first field.
|
||||
fi := fields[i]
|
||||
name := fi.name
|
||||
for advance = 1; i+advance < len(fields); advance++ {
|
||||
fj := fields[i+advance]
|
||||
if fj.name != name {
|
||||
break
|
||||
}
|
||||
}
|
||||
if advance == 1 { // Only one field with this name
|
||||
out = append(out, fi)
|
||||
continue
|
||||
}
|
||||
dominant, ok := dominantField(fields[i : i+advance])
|
||||
if ok {
|
||||
out = append(out, dominant)
|
||||
}
|
||||
}
|
||||
|
||||
fields = out
|
||||
sort.Sort(byIndex(fields))
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to
|
||||
// have the same name, to find the single field that dominates the
|
||||
// others using Go's embedding rules, modified by the presence of
|
||||
// TOML tags. If there are multiple top-level fields, the boolean
|
||||
// will be false: This condition is an error in Go and we skip all
|
||||
// the fields.
|
||||
func dominantField(fields []field) (field, bool) {
|
||||
// The fields are sorted in increasing index-length order. The winner
|
||||
// must therefore be one with the shortest index length. Drop all
|
||||
// longer entries, which is easy: just truncate the slice.
|
||||
length := len(fields[0].index)
|
||||
tagged := -1 // Index of first tagged field.
|
||||
for i, f := range fields {
|
||||
if len(f.index) > length {
|
||||
fields = fields[:i]
|
||||
break
|
||||
}
|
||||
if f.tag {
|
||||
if tagged >= 0 {
|
||||
// Multiple tagged fields at the same level: conflict.
|
||||
// Return no field.
|
||||
return field{}, false
|
||||
}
|
||||
tagged = i
|
||||
}
|
||||
}
|
||||
if tagged >= 0 {
|
||||
return fields[tagged], true
|
||||
}
|
||||
// All remaining fields have the same length. If there's more than one,
|
||||
// we have a conflict (two fields named "X" at the same level) and we
|
||||
// return no field.
|
||||
if len(fields) > 1 {
|
||||
return field{}, false
|
||||
}
|
||||
return fields[0], true
|
||||
}
|
||||
|
||||
var fieldCache struct {
|
||||
sync.RWMutex
|
||||
m map[reflect.Type][]field
|
||||
}
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
func cachedTypeFields(t reflect.Type) []field {
|
||||
fieldCache.RLock()
|
||||
f := fieldCache.m[t]
|
||||
fieldCache.RUnlock()
|
||||
if f != nil {
|
||||
return f
|
||||
}
|
||||
|
||||
// Compute fields without lock.
|
||||
// Might duplicate effort but won't hold other computations back.
|
||||
f = typeFields(t)
|
||||
if f == nil {
|
||||
f = []field{}
|
||||
}
|
||||
|
||||
fieldCache.Lock()
|
||||
if fieldCache.m == nil {
|
||||
fieldCache.m = map[reflect.Type][]field{}
|
||||
}
|
||||
fieldCache.m[t] = f
|
||||
fieldCache.Unlock()
|
||||
return f
|
||||
}
|
19
vendor/github.com/couchbase/gomemcached/LICENSE
generated
vendored
Normal file
19
vendor/github.com/couchbase/gomemcached/LICENSE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2013 Dustin Sallings
|
||||
|
||||
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.
|
1074
vendor/github.com/couchbase/gomemcached/client/mc.go
generated
vendored
Normal file
1074
vendor/github.com/couchbase/gomemcached/client/mc.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
333
vendor/github.com/couchbase/gomemcached/client/tap_feed.go
generated
vendored
Normal file
333
vendor/github.com/couchbase/gomemcached/client/tap_feed.go
generated
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
package memcached
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/couchbase/gomemcached"
|
||||
"github.com/couchbase/goutils/logging"
|
||||
)
|
||||
|
||||
// TAP protocol docs: <http://www.couchbase.com/wiki/display/couchbase/TAP+Protocol>
|
||||
|
||||
// TapOpcode is the tap operation type (found in TapEvent)
|
||||
type TapOpcode uint8
|
||||
|
||||
// Tap opcode values.
|
||||
const (
|
||||
TapBeginBackfill = TapOpcode(iota)
|
||||
TapEndBackfill
|
||||
TapMutation
|
||||
TapDeletion
|
||||
TapCheckpointStart
|
||||
TapCheckpointEnd
|
||||
tapEndStream
|
||||
)
|
||||
|
||||
const tapMutationExtraLen = 16
|
||||
|
||||
var tapOpcodeNames map[TapOpcode]string
|
||||
|
||||
func init() {
|
||||
tapOpcodeNames = map[TapOpcode]string{
|
||||
TapBeginBackfill: "BeginBackfill",
|
||||
TapEndBackfill: "EndBackfill",
|
||||
TapMutation: "Mutation",
|
||||
TapDeletion: "Deletion",
|
||||
TapCheckpointStart: "TapCheckpointStart",
|
||||
TapCheckpointEnd: "TapCheckpointEnd",
|
||||
tapEndStream: "EndStream",
|
||||
}
|
||||
}
|
||||
|
||||
func (opcode TapOpcode) String() string {
|
||||
name := tapOpcodeNames[opcode]
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("#%d", opcode)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// TapEvent is a TAP notification of an operation on the server.
|
||||
type TapEvent struct {
|
||||
Opcode TapOpcode // Type of event
|
||||
VBucket uint16 // VBucket this event applies to
|
||||
Flags uint32 // Item flags
|
||||
Expiry uint32 // Item expiration time
|
||||
Key, Value []byte // Item key/value
|
||||
Cas uint64
|
||||
}
|
||||
|
||||
func makeTapEvent(req gomemcached.MCRequest) *TapEvent {
|
||||
event := TapEvent{
|
||||
VBucket: req.VBucket,
|
||||
}
|
||||
switch req.Opcode {
|
||||
case gomemcached.TAP_MUTATION:
|
||||
event.Opcode = TapMutation
|
||||
event.Key = req.Key
|
||||
event.Value = req.Body
|
||||
event.Cas = req.Cas
|
||||
case gomemcached.TAP_DELETE:
|
||||
event.Opcode = TapDeletion
|
||||
event.Key = req.Key
|
||||
event.Cas = req.Cas
|
||||
case gomemcached.TAP_CHECKPOINT_START:
|
||||
event.Opcode = TapCheckpointStart
|
||||
case gomemcached.TAP_CHECKPOINT_END:
|
||||
event.Opcode = TapCheckpointEnd
|
||||
case gomemcached.TAP_OPAQUE:
|
||||
if len(req.Extras) < 8+4 {
|
||||
return nil
|
||||
}
|
||||
switch op := int(binary.BigEndian.Uint32(req.Extras[8:])); op {
|
||||
case gomemcached.TAP_OPAQUE_INITIAL_VBUCKET_STREAM:
|
||||
event.Opcode = TapBeginBackfill
|
||||
case gomemcached.TAP_OPAQUE_CLOSE_BACKFILL:
|
||||
event.Opcode = TapEndBackfill
|
||||
case gomemcached.TAP_OPAQUE_CLOSE_TAP_STREAM:
|
||||
event.Opcode = tapEndStream
|
||||
case gomemcached.TAP_OPAQUE_ENABLE_AUTO_NACK:
|
||||
return nil
|
||||
case gomemcached.TAP_OPAQUE_ENABLE_CHECKPOINT_SYNC:
|
||||
return nil
|
||||
default:
|
||||
logging.Infof("TapFeed: Ignoring TAP_OPAQUE/%d", op)
|
||||
return nil // unknown opaque event
|
||||
}
|
||||
case gomemcached.NOOP:
|
||||
return nil // ignore
|
||||
default:
|
||||
logging.Infof("TapFeed: Ignoring %s", req.Opcode)
|
||||
return nil // unknown event
|
||||
}
|
||||
|
||||
if len(req.Extras) >= tapMutationExtraLen &&
|
||||
(event.Opcode == TapMutation || event.Opcode == TapDeletion) {
|
||||
|
||||
event.Flags = binary.BigEndian.Uint32(req.Extras[8:])
|
||||
event.Expiry = binary.BigEndian.Uint32(req.Extras[12:])
|
||||
}
|
||||
|
||||
return &event
|
||||
}
|
||||
|
||||
func (event TapEvent) String() string {
|
||||
switch event.Opcode {
|
||||
case TapBeginBackfill, TapEndBackfill, TapCheckpointStart, TapCheckpointEnd:
|
||||
return fmt.Sprintf("<TapEvent %s, vbucket=%d>",
|
||||
event.Opcode, event.VBucket)
|
||||
default:
|
||||
return fmt.Sprintf("<TapEvent %s, key=%q (%d bytes) flags=%x, exp=%d>",
|
||||
event.Opcode, event.Key, len(event.Value),
|
||||
event.Flags, event.Expiry)
|
||||
}
|
||||
}
|
||||
|
||||
// TapArguments are parameters for requesting a TAP feed.
|
||||
//
|
||||
// Call DefaultTapArguments to get a default one.
|
||||
type TapArguments struct {
|
||||
// Timestamp of oldest item to send.
|
||||
//
|
||||
// Use TapNoBackfill to suppress all past items.
|
||||
Backfill uint64
|
||||
// If set, server will disconnect after sending existing items.
|
||||
Dump bool
|
||||
// The indices of the vbuckets to watch; empty/nil to watch all.
|
||||
VBuckets []uint16
|
||||
// Transfers ownership of vbuckets during cluster rebalance.
|
||||
Takeover bool
|
||||
// If true, server will wait for client ACK after every notification.
|
||||
SupportAck bool
|
||||
// If true, client doesn't want values so server shouldn't send them.
|
||||
KeysOnly bool
|
||||
// If true, client wants the server to send checkpoint events.
|
||||
Checkpoint bool
|
||||
// Optional identifier to use for this client, to allow reconnects
|
||||
ClientName string
|
||||
// Registers this client (by name) till explicitly deregistered.
|
||||
RegisteredClient bool
|
||||
}
|
||||
|
||||
// Value for TapArguments.Backfill denoting that no past events at all
|
||||
// should be sent.
|
||||
const TapNoBackfill = math.MaxUint64
|
||||
|
||||
// DefaultTapArguments returns a default set of parameter values to
|
||||
// pass to StartTapFeed.
|
||||
func DefaultTapArguments() TapArguments {
|
||||
return TapArguments{
|
||||
Backfill: TapNoBackfill,
|
||||
}
|
||||
}
|
||||
|
||||
func (args *TapArguments) flags() []byte {
|
||||
var flags gomemcached.TapConnectFlag
|
||||
if args.Backfill != 0 {
|
||||
flags |= gomemcached.BACKFILL
|
||||
}
|
||||
if args.Dump {
|
||||
flags |= gomemcached.DUMP
|
||||
}
|
||||
if len(args.VBuckets) > 0 {
|
||||
flags |= gomemcached.LIST_VBUCKETS
|
||||
}
|
||||
if args.Takeover {
|
||||
flags |= gomemcached.TAKEOVER_VBUCKETS
|
||||
}
|
||||
if args.SupportAck {
|
||||
flags |= gomemcached.SUPPORT_ACK
|
||||
}
|
||||
if args.KeysOnly {
|
||||
flags |= gomemcached.REQUEST_KEYS_ONLY
|
||||
}
|
||||
if args.Checkpoint {
|
||||
flags |= gomemcached.CHECKPOINT
|
||||
}
|
||||
if args.RegisteredClient {
|
||||
flags |= gomemcached.REGISTERED_CLIENT
|
||||
}
|
||||
encoded := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(encoded, uint32(flags))
|
||||
return encoded
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (args *TapArguments) bytes() (rv []byte) {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
if args.Backfill > 0 {
|
||||
must(binary.Write(buf, binary.BigEndian, uint64(args.Backfill)))
|
||||
}
|
||||
|
||||
if len(args.VBuckets) > 0 {
|
||||
must(binary.Write(buf, binary.BigEndian, uint16(len(args.VBuckets))))
|
||||
for i := 0; i < len(args.VBuckets); i++ {
|
||||
must(binary.Write(buf, binary.BigEndian, uint16(args.VBuckets[i])))
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// TapFeed represents a stream of events from a server.
|
||||
type TapFeed struct {
|
||||
C <-chan TapEvent
|
||||
Error error
|
||||
closer chan bool
|
||||
}
|
||||
|
||||
// StartTapFeed starts a TAP feed on a client connection.
|
||||
//
|
||||
// The events can be read from the returned channel. The connection
|
||||
// can no longer be used for other purposes; it's now reserved for
|
||||
// receiving the TAP messages. To stop receiving events, close the
|
||||
// client connection.
|
||||
func (mc *Client) StartTapFeed(args TapArguments) (*TapFeed, error) {
|
||||
rq := &gomemcached.MCRequest{
|
||||
Opcode: gomemcached.TAP_CONNECT,
|
||||
Key: []byte(args.ClientName),
|
||||
Extras: args.flags(),
|
||||
Body: args.bytes()}
|
||||
|
||||
err := mc.Transmit(rq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ch := make(chan TapEvent)
|
||||
feed := &TapFeed{
|
||||
C: ch,
|
||||
closer: make(chan bool),
|
||||
}
|
||||
go mc.runFeed(ch, feed)
|
||||
return feed, nil
|
||||
}
|
||||
|
||||
// TapRecvHook is called after every incoming tap packet is received.
|
||||
var TapRecvHook func(*gomemcached.MCRequest, int, error)
|
||||
|
||||
// Internal goroutine that reads from the socket and writes events to
|
||||
// the channel
|
||||
func (mc *Client) runFeed(ch chan TapEvent, feed *TapFeed) {
|
||||
defer close(ch)
|
||||
var headerBuf [gomemcached.HDR_LEN]byte
|
||||
loop:
|
||||
for {
|
||||
// Read the next request from the server.
|
||||
//
|
||||
// (Can't call mc.Receive() because it reads a
|
||||
// _response_ not a request.)
|
||||
var pkt gomemcached.MCRequest
|
||||
n, err := pkt.Receive(mc.conn, headerBuf[:])
|
||||
if TapRecvHook != nil {
|
||||
TapRecvHook(&pkt, n, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
feed.Error = err
|
||||
}
|
||||
break loop
|
||||
}
|
||||
|
||||
//logging.Infof("** TapFeed received %#v : %q", pkt, pkt.Body)
|
||||
|
||||
if pkt.Opcode == gomemcached.TAP_CONNECT {
|
||||
// This is not an event from the server; it's
|
||||
// an error response to my connect request.
|
||||
feed.Error = fmt.Errorf("tap connection failed: %s", pkt.Body)
|
||||
break loop
|
||||
}
|
||||
|
||||
event := makeTapEvent(pkt)
|
||||
if event != nil {
|
||||
if event.Opcode == tapEndStream {
|
||||
break loop
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- *event:
|
||||
case <-feed.closer:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if len(pkt.Extras) >= 4 {
|
||||
reqFlags := binary.BigEndian.Uint16(pkt.Extras[2:])
|
||||
if reqFlags&gomemcached.TAP_ACK != 0 {
|
||||
if _, err := mc.sendAck(&pkt); err != nil {
|
||||
feed.Error = err
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := mc.Close(); err != nil {
|
||||
logging.Errorf("Error closing memcached client: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *Client) sendAck(pkt *gomemcached.MCRequest) (int, error) {
|
||||
res := gomemcached.MCResponse{
|
||||
Opcode: pkt.Opcode,
|
||||
Opaque: pkt.Opaque,
|
||||
Status: gomemcached.SUCCESS,
|
||||
}
|
||||
return res.Transmit(mc.conn)
|
||||
}
|
||||
|
||||
// Close terminates a TapFeed.
|
||||
//
|
||||
// Call this if you stop using a TapFeed before its channel ends.
|
||||
func (feed *TapFeed) Close() {
|
||||
close(feed.closer)
|
||||
}
|
67
vendor/github.com/couchbase/gomemcached/client/transport.go
generated
vendored
Normal file
67
vendor/github.com/couchbase/gomemcached/client/transport.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package memcached
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/couchbase/gomemcached"
|
||||
)
|
||||
|
||||
var errNoConn = errors.New("no connection")
|
||||
|
||||
// UnwrapMemcachedError converts memcached errors to normal responses.
|
||||
//
|
||||
// If the error is a memcached response, declare the error to be nil
|
||||
// so a client can handle the status without worrying about whether it
|
||||
// indicates success or failure.
|
||||
func UnwrapMemcachedError(rv *gomemcached.MCResponse,
|
||||
err error) (*gomemcached.MCResponse, error) {
|
||||
|
||||
if rv == err {
|
||||
return rv, nil
|
||||
}
|
||||
return rv, err
|
||||
}
|
||||
|
||||
// ReceiveHook is called after every packet is received (or attempted to be)
|
||||
var ReceiveHook func(*gomemcached.MCResponse, int, error)
|
||||
|
||||
func getResponse(s io.Reader, hdrBytes []byte) (rv *gomemcached.MCResponse, n int, err error) {
|
||||
if s == nil {
|
||||
return nil, 0, errNoConn
|
||||
}
|
||||
|
||||
rv = &gomemcached.MCResponse{}
|
||||
n, err = rv.Receive(s, hdrBytes)
|
||||
|
||||
if ReceiveHook != nil {
|
||||
ReceiveHook(rv, n, err)
|
||||
}
|
||||
|
||||
if err == nil && (rv.Status != gomemcached.SUCCESS && rv.Status != gomemcached.AUTH_CONTINUE) {
|
||||
err = rv
|
||||
}
|
||||
return rv, n, err
|
||||
}
|
||||
|
||||
// TransmitHook is called after each packet is transmitted.
|
||||
var TransmitHook func(*gomemcached.MCRequest, int, error)
|
||||
|
||||
func transmitRequest(o io.Writer, req *gomemcached.MCRequest) (int, error) {
|
||||
if o == nil {
|
||||
return 0, errNoConn
|
||||
}
|
||||
n, err := req.Transmit(o)
|
||||
if TransmitHook != nil {
|
||||
TransmitHook(req, n, err)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func transmitResponse(o io.Writer, res *gomemcached.MCResponse) (int, error) {
|
||||
if o == nil {
|
||||
return 0, errNoConn
|
||||
}
|
||||
n, err := res.Transmit(o)
|
||||
return n, err
|
||||
}
|
1005
vendor/github.com/couchbase/gomemcached/client/upr_feed.go
generated
vendored
Normal file
1005
vendor/github.com/couchbase/gomemcached/client/upr_feed.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
335
vendor/github.com/couchbase/gomemcached/mc_constants.go
generated
vendored
Normal file
335
vendor/github.com/couchbase/gomemcached/mc_constants.go
generated
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
// Package gomemcached is binary protocol packet formats and constants.
|
||||
package gomemcached
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
REQ_MAGIC = 0x80
|
||||
RES_MAGIC = 0x81
|
||||
)
|
||||
|
||||
// CommandCode for memcached packets.
|
||||
type CommandCode uint8
|
||||
|
||||
const (
|
||||
GET = CommandCode(0x00)
|
||||
SET = CommandCode(0x01)
|
||||
ADD = CommandCode(0x02)
|
||||
REPLACE = CommandCode(0x03)
|
||||
DELETE = CommandCode(0x04)
|
||||
INCREMENT = CommandCode(0x05)
|
||||
DECREMENT = CommandCode(0x06)
|
||||
QUIT = CommandCode(0x07)
|
||||
FLUSH = CommandCode(0x08)
|
||||
GETQ = CommandCode(0x09)
|
||||
NOOP = CommandCode(0x0a)
|
||||
VERSION = CommandCode(0x0b)
|
||||
GETK = CommandCode(0x0c)
|
||||
GETKQ = CommandCode(0x0d)
|
||||
APPEND = CommandCode(0x0e)
|
||||
PREPEND = CommandCode(0x0f)
|
||||
STAT = CommandCode(0x10)
|
||||
SETQ = CommandCode(0x11)
|
||||
ADDQ = CommandCode(0x12)
|
||||
REPLACEQ = CommandCode(0x13)
|
||||
DELETEQ = CommandCode(0x14)
|
||||
INCREMENTQ = CommandCode(0x15)
|
||||
DECREMENTQ = CommandCode(0x16)
|
||||
QUITQ = CommandCode(0x17)
|
||||
FLUSHQ = CommandCode(0x18)
|
||||
APPENDQ = CommandCode(0x19)
|
||||
AUDIT = CommandCode(0x27)
|
||||
PREPENDQ = CommandCode(0x1a)
|
||||
GAT = CommandCode(0x1d)
|
||||
HELLO = CommandCode(0x1f)
|
||||
RGET = CommandCode(0x30)
|
||||
RSET = CommandCode(0x31)
|
||||
RSETQ = CommandCode(0x32)
|
||||
RAPPEND = CommandCode(0x33)
|
||||
RAPPENDQ = CommandCode(0x34)
|
||||
RPREPEND = CommandCode(0x35)
|
||||
RPREPENDQ = CommandCode(0x36)
|
||||
RDELETE = CommandCode(0x37)
|
||||
RDELETEQ = CommandCode(0x38)
|
||||
RINCR = CommandCode(0x39)
|
||||
RINCRQ = CommandCode(0x3a)
|
||||
RDECR = CommandCode(0x3b)
|
||||
RDECRQ = CommandCode(0x3c)
|
||||
|
||||
SASL_LIST_MECHS = CommandCode(0x20)
|
||||
SASL_AUTH = CommandCode(0x21)
|
||||
SASL_STEP = CommandCode(0x22)
|
||||
|
||||
SET_VBUCKET = CommandCode(0x3d)
|
||||
|
||||
TAP_CONNECT = CommandCode(0x40) // Client-sent request to initiate Tap feed
|
||||
TAP_MUTATION = CommandCode(0x41) // Notification of a SET/ADD/REPLACE/etc. on the server
|
||||
TAP_DELETE = CommandCode(0x42) // Notification of a DELETE on the server
|
||||
TAP_FLUSH = CommandCode(0x43) // Replicates a flush_all command
|
||||
TAP_OPAQUE = CommandCode(0x44) // Opaque control data from the engine
|
||||
TAP_VBUCKET_SET = CommandCode(0x45) // Sets state of vbucket in receiver (used in takeover)
|
||||
TAP_CHECKPOINT_START = CommandCode(0x46) // Notifies start of new checkpoint
|
||||
TAP_CHECKPOINT_END = CommandCode(0x47) // Notifies end of checkpoint
|
||||
|
||||
UPR_OPEN = CommandCode(0x50) // Open a UPR connection with a name
|
||||
UPR_ADDSTREAM = CommandCode(0x51) // Sent by ebucketMigrator to UPR Consumer
|
||||
UPR_CLOSESTREAM = CommandCode(0x52) // Sent by eBucketMigrator to UPR Consumer
|
||||
UPR_FAILOVERLOG = CommandCode(0x54) // Request failover logs
|
||||
UPR_STREAMREQ = CommandCode(0x53) // Stream request from consumer to producer
|
||||
UPR_STREAMEND = CommandCode(0x55) // Sent by producer when it has no more messages to stream
|
||||
UPR_SNAPSHOT = CommandCode(0x56) // Start of a new snapshot
|
||||
UPR_MUTATION = CommandCode(0x57) // Key mutation
|
||||
UPR_DELETION = CommandCode(0x58) // Key deletion
|
||||
UPR_EXPIRATION = CommandCode(0x59) // Key expiration
|
||||
UPR_FLUSH = CommandCode(0x5a) // Delete all the data for a vbucket
|
||||
UPR_NOOP = CommandCode(0x5c) // UPR NOOP
|
||||
UPR_BUFFERACK = CommandCode(0x5d) // UPR Buffer Acknowledgement
|
||||
UPR_CONTROL = CommandCode(0x5e) // Set flow control params
|
||||
|
||||
SELECT_BUCKET = CommandCode(0x89) // Select bucket
|
||||
|
||||
OBSERVE_SEQNO = CommandCode(0x91) // Sequence Number based Observe
|
||||
OBSERVE = CommandCode(0x92)
|
||||
|
||||
GET_META = CommandCode(0xA0) // Get meta. returns with expiry, flags, cas etc
|
||||
SUBDOC_GET = CommandCode(0xc5) // Get subdoc. Returns with xattrs
|
||||
SUBDOC_MULTI_LOOKUP = CommandCode(0xd0) // Multi lookup. Doc xattrs and meta.
|
||||
)
|
||||
|
||||
// command codes that are counted toward DCP control buffer
|
||||
// when DCP clients receive DCP messages with these command codes, they need to provide acknowledgement
|
||||
var BufferedCommandCodeMap = map[CommandCode]bool{
|
||||
SET_VBUCKET: true,
|
||||
UPR_STREAMEND: true,
|
||||
UPR_SNAPSHOT: true,
|
||||
UPR_MUTATION: true,
|
||||
UPR_DELETION: true,
|
||||
UPR_EXPIRATION: true}
|
||||
|
||||
// Status field for memcached response.
|
||||
type Status uint16
|
||||
|
||||
// Matches with protocol_binary.h as source of truth
|
||||
const (
|
||||
SUCCESS = Status(0x00)
|
||||
KEY_ENOENT = Status(0x01)
|
||||
KEY_EEXISTS = Status(0x02)
|
||||
E2BIG = Status(0x03)
|
||||
EINVAL = Status(0x04)
|
||||
NOT_STORED = Status(0x05)
|
||||
DELTA_BADVAL = Status(0x06)
|
||||
NOT_MY_VBUCKET = Status(0x07)
|
||||
NO_BUCKET = Status(0x08)
|
||||
LOCKED = Status(0x09)
|
||||
AUTH_STALE = Status(0x1f)
|
||||
AUTH_ERROR = Status(0x20)
|
||||
AUTH_CONTINUE = Status(0x21)
|
||||
ERANGE = Status(0x22)
|
||||
ROLLBACK = Status(0x23)
|
||||
EACCESS = Status(0x24)
|
||||
NOT_INITIALIZED = Status(0x25)
|
||||
UNKNOWN_COMMAND = Status(0x81)
|
||||
ENOMEM = Status(0x82)
|
||||
NOT_SUPPORTED = Status(0x83)
|
||||
EINTERNAL = Status(0x84)
|
||||
EBUSY = Status(0x85)
|
||||
TMPFAIL = Status(0x86)
|
||||
|
||||
// SUBDOC
|
||||
SUBDOC_PATH_NOT_FOUND = Status(0xc0)
|
||||
SUBDOC_BAD_MULTI = Status(0xcc)
|
||||
SUBDOC_MULTI_PATH_FAILURE_DELETED = Status(0xd3)
|
||||
)
|
||||
|
||||
// for log redaction
|
||||
const (
|
||||
UdTagBegin = "<ud>"
|
||||
UdTagEnd = "</ud>"
|
||||
)
|
||||
|
||||
var isFatal = map[Status]bool{
|
||||
DELTA_BADVAL: true,
|
||||
NO_BUCKET: true,
|
||||
AUTH_STALE: true,
|
||||
AUTH_ERROR: true,
|
||||
ERANGE: true,
|
||||
ROLLBACK: true,
|
||||
EACCESS: true,
|
||||
ENOMEM: true,
|
||||
NOT_SUPPORTED: true,
|
||||
}
|
||||
|
||||
// the producer/consumer bit in dcp flags
|
||||
var DCP_PRODUCER uint32 = 0x01
|
||||
|
||||
// the include XATTRS bit in dcp flags
|
||||
var DCP_OPEN_INCLUDE_XATTRS uint32 = 0x04
|
||||
|
||||
// the include deletion time bit in dcp flags
|
||||
var DCP_OPEN_INCLUDE_DELETE_TIMES uint32 = 0x20
|
||||
|
||||
// Datatype to Include XATTRS in SUBDOC GET
|
||||
var SUBDOC_FLAG_XATTR uint8 = 0x04
|
||||
|
||||
// MCItem is an internal representation of an item.
|
||||
type MCItem struct {
|
||||
Cas uint64
|
||||
Flags, Expiration uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// Number of bytes in a binary protocol header.
|
||||
const HDR_LEN = 24
|
||||
|
||||
// Mapping of CommandCode -> name of command (not exhaustive)
|
||||
var CommandNames map[CommandCode]string
|
||||
|
||||
// StatusNames human readable names for memcached response.
|
||||
var StatusNames map[Status]string
|
||||
|
||||
func init() {
|
||||
CommandNames = make(map[CommandCode]string)
|
||||
CommandNames[GET] = "GET"
|
||||
CommandNames[SET] = "SET"
|
||||
CommandNames[ADD] = "ADD"
|
||||
CommandNames[REPLACE] = "REPLACE"
|
||||
CommandNames[DELETE] = "DELETE"
|
||||
CommandNames[INCREMENT] = "INCREMENT"
|
||||
CommandNames[DECREMENT] = "DECREMENT"
|
||||
CommandNames[QUIT] = "QUIT"
|
||||
CommandNames[FLUSH] = "FLUSH"
|
||||
CommandNames[GETQ] = "GETQ"
|
||||
CommandNames[NOOP] = "NOOP"
|
||||
CommandNames[VERSION] = "VERSION"
|
||||
CommandNames[GETK] = "GETK"
|
||||
CommandNames[GETKQ] = "GETKQ"
|
||||
CommandNames[APPEND] = "APPEND"
|
||||
CommandNames[PREPEND] = "PREPEND"
|
||||
CommandNames[STAT] = "STAT"
|
||||
CommandNames[SETQ] = "SETQ"
|
||||
CommandNames[ADDQ] = "ADDQ"
|
||||
CommandNames[REPLACEQ] = "REPLACEQ"
|
||||
CommandNames[DELETEQ] = "DELETEQ"
|
||||
CommandNames[INCREMENTQ] = "INCREMENTQ"
|
||||
CommandNames[DECREMENTQ] = "DECREMENTQ"
|
||||
CommandNames[QUITQ] = "QUITQ"
|
||||
CommandNames[FLUSHQ] = "FLUSHQ"
|
||||
CommandNames[APPENDQ] = "APPENDQ"
|
||||
CommandNames[PREPENDQ] = "PREPENDQ"
|
||||
CommandNames[RGET] = "RGET"
|
||||
CommandNames[RSET] = "RSET"
|
||||
CommandNames[RSETQ] = "RSETQ"
|
||||
CommandNames[RAPPEND] = "RAPPEND"
|
||||
CommandNames[RAPPENDQ] = "RAPPENDQ"
|
||||
CommandNames[RPREPEND] = "RPREPEND"
|
||||
CommandNames[RPREPENDQ] = "RPREPENDQ"
|
||||
CommandNames[RDELETE] = "RDELETE"
|
||||
CommandNames[RDELETEQ] = "RDELETEQ"
|
||||
CommandNames[RINCR] = "RINCR"
|
||||
CommandNames[RINCRQ] = "RINCRQ"
|
||||
CommandNames[RDECR] = "RDECR"
|
||||
CommandNames[RDECRQ] = "RDECRQ"
|
||||
|
||||
CommandNames[SASL_LIST_MECHS] = "SASL_LIST_MECHS"
|
||||
CommandNames[SASL_AUTH] = "SASL_AUTH"
|
||||
CommandNames[SASL_STEP] = "SASL_STEP"
|
||||
|
||||
CommandNames[TAP_CONNECT] = "TAP_CONNECT"
|
||||
CommandNames[TAP_MUTATION] = "TAP_MUTATION"
|
||||
CommandNames[TAP_DELETE] = "TAP_DELETE"
|
||||
CommandNames[TAP_FLUSH] = "TAP_FLUSH"
|
||||
CommandNames[TAP_OPAQUE] = "TAP_OPAQUE"
|
||||
CommandNames[TAP_VBUCKET_SET] = "TAP_VBUCKET_SET"
|
||||
CommandNames[TAP_CHECKPOINT_START] = "TAP_CHECKPOINT_START"
|
||||
CommandNames[TAP_CHECKPOINT_END] = "TAP_CHECKPOINT_END"
|
||||
|
||||
CommandNames[UPR_OPEN] = "UPR_OPEN"
|
||||
CommandNames[UPR_ADDSTREAM] = "UPR_ADDSTREAM"
|
||||
CommandNames[UPR_CLOSESTREAM] = "UPR_CLOSESTREAM"
|
||||
CommandNames[UPR_FAILOVERLOG] = "UPR_FAILOVERLOG"
|
||||
CommandNames[UPR_STREAMREQ] = "UPR_STREAMREQ"
|
||||
CommandNames[UPR_STREAMEND] = "UPR_STREAMEND"
|
||||
CommandNames[UPR_SNAPSHOT] = "UPR_SNAPSHOT"
|
||||
CommandNames[UPR_MUTATION] = "UPR_MUTATION"
|
||||
CommandNames[UPR_DELETION] = "UPR_DELETION"
|
||||
CommandNames[UPR_EXPIRATION] = "UPR_EXPIRATION"
|
||||
CommandNames[UPR_FLUSH] = "UPR_FLUSH"
|
||||
CommandNames[UPR_NOOP] = "UPR_NOOP"
|
||||
CommandNames[UPR_BUFFERACK] = "UPR_BUFFERACK"
|
||||
CommandNames[UPR_CONTROL] = "UPR_CONTROL"
|
||||
CommandNames[SUBDOC_GET] = "SUBDOC_GET"
|
||||
CommandNames[SUBDOC_MULTI_LOOKUP] = "SUBDOC_MULTI_LOOKUP"
|
||||
|
||||
StatusNames = make(map[Status]string)
|
||||
StatusNames[SUCCESS] = "SUCCESS"
|
||||
StatusNames[KEY_ENOENT] = "KEY_ENOENT"
|
||||
StatusNames[KEY_EEXISTS] = "KEY_EEXISTS"
|
||||
StatusNames[E2BIG] = "E2BIG"
|
||||
StatusNames[EINVAL] = "EINVAL"
|
||||
StatusNames[NOT_STORED] = "NOT_STORED"
|
||||
StatusNames[DELTA_BADVAL] = "DELTA_BADVAL"
|
||||
StatusNames[NOT_MY_VBUCKET] = "NOT_MY_VBUCKET"
|
||||
StatusNames[NO_BUCKET] = "NO_BUCKET"
|
||||
StatusNames[AUTH_STALE] = "AUTH_STALE"
|
||||
StatusNames[AUTH_ERROR] = "AUTH_ERROR"
|
||||
StatusNames[AUTH_CONTINUE] = "AUTH_CONTINUE"
|
||||
StatusNames[ERANGE] = "ERANGE"
|
||||
StatusNames[ROLLBACK] = "ROLLBACK"
|
||||
StatusNames[EACCESS] = "EACCESS"
|
||||
StatusNames[NOT_INITIALIZED] = "NOT_INITIALIZED"
|
||||
StatusNames[UNKNOWN_COMMAND] = "UNKNOWN_COMMAND"
|
||||
StatusNames[ENOMEM] = "ENOMEM"
|
||||
StatusNames[NOT_SUPPORTED] = "NOT_SUPPORTED"
|
||||
StatusNames[EINTERNAL] = "EINTERNAL"
|
||||
StatusNames[EBUSY] = "EBUSY"
|
||||
StatusNames[TMPFAIL] = "TMPFAIL"
|
||||
StatusNames[SUBDOC_PATH_NOT_FOUND] = "SUBDOC_PATH_NOT_FOUND"
|
||||
StatusNames[SUBDOC_BAD_MULTI] = "SUBDOC_BAD_MULTI"
|
||||
|
||||
}
|
||||
|
||||
// String an op code.
|
||||
func (o CommandCode) String() (rv string) {
|
||||
rv = CommandNames[o]
|
||||
if rv == "" {
|
||||
rv = fmt.Sprintf("0x%02x", int(o))
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// String an op code.
|
||||
func (s Status) String() (rv string) {
|
||||
rv = StatusNames[s]
|
||||
if rv == "" {
|
||||
rv = fmt.Sprintf("0x%02x", int(s))
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// IsQuiet will return true if a command is a "quiet" command.
|
||||
func (o CommandCode) IsQuiet() bool {
|
||||
switch o {
|
||||
case GETQ,
|
||||
GETKQ,
|
||||
SETQ,
|
||||
ADDQ,
|
||||
REPLACEQ,
|
||||
DELETEQ,
|
||||
INCREMENTQ,
|
||||
DECREMENTQ,
|
||||
QUITQ,
|
||||
FLUSHQ,
|
||||
APPENDQ,
|
||||
PREPENDQ,
|
||||
RSETQ,
|
||||
RAPPENDQ,
|
||||
RPREPENDQ,
|
||||
RDELETEQ,
|
||||
RINCRQ,
|
||||
RDECRQ:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
197
vendor/github.com/couchbase/gomemcached/mc_req.go
generated
vendored
Normal file
197
vendor/github.com/couchbase/gomemcached/mc_req.go
generated
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
package gomemcached
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// The maximum reasonable body length to expect.
|
||||
// Anything larger than this will result in an error.
|
||||
// The current limit, 20MB, is the size limit supported by ep-engine.
|
||||
var MaxBodyLen = int(20 * 1024 * 1024)
|
||||
|
||||
// MCRequest is memcached Request
|
||||
type MCRequest struct {
|
||||
// The command being issued
|
||||
Opcode CommandCode
|
||||
// The CAS (if applicable, or 0)
|
||||
Cas uint64
|
||||
// An opaque value to be returned with this request
|
||||
Opaque uint32
|
||||
// The vbucket to which this command belongs
|
||||
VBucket uint16
|
||||
// Command extras, key, and body
|
||||
Extras, Key, Body, ExtMeta []byte
|
||||
// Datatype identifier
|
||||
DataType uint8
|
||||
}
|
||||
|
||||
// Size gives the number of bytes this request requires.
|
||||
func (req *MCRequest) Size() int {
|
||||
return HDR_LEN + len(req.Extras) + len(req.Key) + len(req.Body) + len(req.ExtMeta)
|
||||
}
|
||||
|
||||
// A debugging string representation of this request
|
||||
func (req MCRequest) String() string {
|
||||
return fmt.Sprintf("{MCRequest opcode=%s, bodylen=%d, key='%s'}",
|
||||
req.Opcode, len(req.Body), req.Key)
|
||||
}
|
||||
|
||||
func (req *MCRequest) fillHeaderBytes(data []byte) int {
|
||||
|
||||
pos := 0
|
||||
data[pos] = REQ_MAGIC
|
||||
pos++
|
||||
data[pos] = byte(req.Opcode)
|
||||
pos++
|
||||
binary.BigEndian.PutUint16(data[pos:pos+2],
|
||||
uint16(len(req.Key)))
|
||||
pos += 2
|
||||
|
||||
// 4
|
||||
data[pos] = byte(len(req.Extras))
|
||||
pos++
|
||||
// Data type
|
||||
if req.DataType != 0 {
|
||||
data[pos] = byte(req.DataType)
|
||||
}
|
||||
pos++
|
||||
binary.BigEndian.PutUint16(data[pos:pos+2], req.VBucket)
|
||||
pos += 2
|
||||
|
||||
// 8
|
||||
binary.BigEndian.PutUint32(data[pos:pos+4],
|
||||
uint32(len(req.Body)+len(req.Key)+len(req.Extras)+len(req.ExtMeta)))
|
||||
pos += 4
|
||||
|
||||
// 12
|
||||
binary.BigEndian.PutUint32(data[pos:pos+4], req.Opaque)
|
||||
pos += 4
|
||||
|
||||
// 16
|
||||
if req.Cas != 0 {
|
||||
binary.BigEndian.PutUint64(data[pos:pos+8], req.Cas)
|
||||
}
|
||||
pos += 8
|
||||
|
||||
if len(req.Extras) > 0 {
|
||||
copy(data[pos:pos+len(req.Extras)], req.Extras)
|
||||
pos += len(req.Extras)
|
||||
}
|
||||
|
||||
if len(req.Key) > 0 {
|
||||
copy(data[pos:pos+len(req.Key)], req.Key)
|
||||
pos += len(req.Key)
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
// HeaderBytes will return the wire representation of the request header
|
||||
// (with the extras and key).
|
||||
func (req *MCRequest) HeaderBytes() []byte {
|
||||
data := make([]byte, HDR_LEN+len(req.Extras)+len(req.Key))
|
||||
|
||||
req.fillHeaderBytes(data)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Bytes will return the wire representation of this request.
|
||||
func (req *MCRequest) Bytes() []byte {
|
||||
data := make([]byte, req.Size())
|
||||
|
||||
pos := req.fillHeaderBytes(data)
|
||||
|
||||
if len(req.Body) > 0 {
|
||||
copy(data[pos:pos+len(req.Body)], req.Body)
|
||||
}
|
||||
|
||||
if len(req.ExtMeta) > 0 {
|
||||
copy(data[pos+len(req.Body):pos+len(req.Body)+len(req.ExtMeta)], req.ExtMeta)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Transmit will send this request message across a writer.
|
||||
func (req *MCRequest) Transmit(w io.Writer) (n int, err error) {
|
||||
if len(req.Body) < 128 {
|
||||
n, err = w.Write(req.Bytes())
|
||||
} else {
|
||||
n, err = w.Write(req.HeaderBytes())
|
||||
if err == nil {
|
||||
m := 0
|
||||
m, err = w.Write(req.Body)
|
||||
n += m
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Receive will fill this MCRequest with the data from a reader.
|
||||
func (req *MCRequest) Receive(r io.Reader, hdrBytes []byte) (int, error) {
|
||||
if len(hdrBytes) < HDR_LEN {
|
||||
hdrBytes = []byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0}
|
||||
}
|
||||
n, err := io.ReadFull(r, hdrBytes)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if hdrBytes[0] != RES_MAGIC && hdrBytes[0] != REQ_MAGIC {
|
||||
return n, fmt.Errorf("bad magic: 0x%02x", hdrBytes[0])
|
||||
}
|
||||
|
||||
klen := int(binary.BigEndian.Uint16(hdrBytes[2:]))
|
||||
elen := int(hdrBytes[4])
|
||||
// Data type at 5
|
||||
req.DataType = uint8(hdrBytes[5])
|
||||
|
||||
req.Opcode = CommandCode(hdrBytes[1])
|
||||
// Vbucket at 6:7
|
||||
req.VBucket = binary.BigEndian.Uint16(hdrBytes[6:])
|
||||
totalBodyLen := int(binary.BigEndian.Uint32(hdrBytes[8:]))
|
||||
|
||||
req.Opaque = binary.BigEndian.Uint32(hdrBytes[12:])
|
||||
req.Cas = binary.BigEndian.Uint64(hdrBytes[16:])
|
||||
|
||||
if totalBodyLen > 0 {
|
||||
buf := make([]byte, totalBodyLen)
|
||||
m, err := io.ReadFull(r, buf)
|
||||
n += m
|
||||
if err == nil {
|
||||
if req.Opcode >= TAP_MUTATION &&
|
||||
req.Opcode <= TAP_CHECKPOINT_END &&
|
||||
len(buf) > 1 {
|
||||
// In these commands there is "engine private"
|
||||
// data at the end of the extras. The first 2
|
||||
// bytes of extra data give its length.
|
||||
elen += int(binary.BigEndian.Uint16(buf))
|
||||
}
|
||||
|
||||
req.Extras = buf[0:elen]
|
||||
req.Key = buf[elen : klen+elen]
|
||||
|
||||
// get the length of extended metadata
|
||||
extMetaLen := 0
|
||||
if elen > 29 {
|
||||
extMetaLen = int(binary.BigEndian.Uint16(req.Extras[28:30]))
|
||||
}
|
||||
|
||||
bodyLen := totalBodyLen - klen - elen - extMetaLen
|
||||
if bodyLen > MaxBodyLen {
|
||||
return n, fmt.Errorf("%d is too big (max %d)",
|
||||
bodyLen, MaxBodyLen)
|
||||
}
|
||||
|
||||
req.Body = buf[klen+elen : klen+elen+bodyLen]
|
||||
req.ExtMeta = buf[klen+elen+bodyLen:]
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
267
vendor/github.com/couchbase/gomemcached/mc_res.go
generated
vendored
Normal file
267
vendor/github.com/couchbase/gomemcached/mc_res.go
generated
vendored
Normal file
@ -0,0 +1,267 @@
|
||||
package gomemcached
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MCResponse is memcached response
|
||||
type MCResponse struct {
|
||||
// The command opcode of the command that sent the request
|
||||
Opcode CommandCode
|
||||
// The status of the response
|
||||
Status Status
|
||||
// The opaque sent in the request
|
||||
Opaque uint32
|
||||
// The CAS identifier (if applicable)
|
||||
Cas uint64
|
||||
// Extras, key, and body for this response
|
||||
Extras, Key, Body []byte
|
||||
// If true, this represents a fatal condition and we should hang up
|
||||
Fatal bool
|
||||
// Datatype identifier
|
||||
DataType uint8
|
||||
}
|
||||
|
||||
// A debugging string representation of this response
|
||||
func (res MCResponse) String() string {
|
||||
return fmt.Sprintf("{MCResponse status=%v keylen=%d, extralen=%d, bodylen=%d}",
|
||||
res.Status, len(res.Key), len(res.Extras), len(res.Body))
|
||||
}
|
||||
|
||||
// Response as an error.
|
||||
func (res *MCResponse) Error() string {
|
||||
return fmt.Sprintf("MCResponse status=%v, opcode=%v, opaque=%v, msg: %s",
|
||||
res.Status, res.Opcode, res.Opaque, string(res.Body))
|
||||
}
|
||||
|
||||
func errStatus(e error) Status {
|
||||
status := Status(0xffff)
|
||||
if res, ok := e.(*MCResponse); ok {
|
||||
status = res.Status
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
// IsNotFound is true if this error represents a "not found" response.
|
||||
func IsNotFound(e error) bool {
|
||||
return errStatus(e) == KEY_ENOENT
|
||||
}
|
||||
|
||||
// IsFatal is false if this error isn't believed to be fatal to a connection.
|
||||
func IsFatal(e error) bool {
|
||||
if e == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := isFatal[errStatus(e)]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Size is number of bytes this response consumes on the wire.
|
||||
func (res *MCResponse) Size() int {
|
||||
return HDR_LEN + len(res.Extras) + len(res.Key) + len(res.Body)
|
||||
}
|
||||
|
||||
func (res *MCResponse) fillHeaderBytes(data []byte) int {
|
||||
pos := 0
|
||||
data[pos] = RES_MAGIC
|
||||
pos++
|
||||
data[pos] = byte(res.Opcode)
|
||||
pos++
|
||||
binary.BigEndian.PutUint16(data[pos:pos+2],
|
||||
uint16(len(res.Key)))
|
||||
pos += 2
|
||||
|
||||
// 4
|
||||
data[pos] = byte(len(res.Extras))
|
||||
pos++
|
||||
// Data type
|
||||
if res.DataType != 0 {
|
||||
data[pos] = byte(res.DataType)
|
||||
} else {
|
||||
data[pos] = 0
|
||||
}
|
||||
pos++
|
||||
binary.BigEndian.PutUint16(data[pos:pos+2], uint16(res.Status))
|
||||
pos += 2
|
||||
|
||||
// 8
|
||||
binary.BigEndian.PutUint32(data[pos:pos+4],
|
||||
uint32(len(res.Body)+len(res.Key)+len(res.Extras)))
|
||||
pos += 4
|
||||
|
||||
// 12
|
||||
binary.BigEndian.PutUint32(data[pos:pos+4], res.Opaque)
|
||||
pos += 4
|
||||
|
||||
// 16
|
||||
binary.BigEndian.PutUint64(data[pos:pos+8], res.Cas)
|
||||
pos += 8
|
||||
|
||||
if len(res.Extras) > 0 {
|
||||
copy(data[pos:pos+len(res.Extras)], res.Extras)
|
||||
pos += len(res.Extras)
|
||||
}
|
||||
|
||||
if len(res.Key) > 0 {
|
||||
copy(data[pos:pos+len(res.Key)], res.Key)
|
||||
pos += len(res.Key)
|
||||
}
|
||||
|
||||
return pos
|
||||
}
|
||||
|
||||
// HeaderBytes will get just the header bytes for this response.
|
||||
func (res *MCResponse) HeaderBytes() []byte {
|
||||
data := make([]byte, HDR_LEN+len(res.Extras)+len(res.Key))
|
||||
|
||||
res.fillHeaderBytes(data)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Bytes will return the actual bytes transmitted for this response.
|
||||
func (res *MCResponse) Bytes() []byte {
|
||||
data := make([]byte, res.Size())
|
||||
|
||||
pos := res.fillHeaderBytes(data)
|
||||
|
||||
copy(data[pos:pos+len(res.Body)], res.Body)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Transmit will send this response message across a writer.
|
||||
func (res *MCResponse) Transmit(w io.Writer) (n int, err error) {
|
||||
if len(res.Body) < 128 {
|
||||
n, err = w.Write(res.Bytes())
|
||||
} else {
|
||||
n, err = w.Write(res.HeaderBytes())
|
||||
if err == nil {
|
||||
m := 0
|
||||
m, err = w.Write(res.Body)
|
||||
m += n
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Receive will fill this MCResponse with the data from this reader.
|
||||
func (res *MCResponse) Receive(r io.Reader, hdrBytes []byte) (n int, err error) {
|
||||
if len(hdrBytes) < HDR_LEN {
|
||||
hdrBytes = []byte{
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0}
|
||||
}
|
||||
n, err = io.ReadFull(r, hdrBytes)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if hdrBytes[0] != RES_MAGIC && hdrBytes[0] != REQ_MAGIC {
|
||||
return n, fmt.Errorf("bad magic: 0x%02x", hdrBytes[0])
|
||||
}
|
||||
|
||||
klen := int(binary.BigEndian.Uint16(hdrBytes[2:4]))
|
||||
elen := int(hdrBytes[4])
|
||||
|
||||
res.Opcode = CommandCode(hdrBytes[1])
|
||||
res.DataType = uint8(hdrBytes[5])
|
||||
res.Status = Status(binary.BigEndian.Uint16(hdrBytes[6:8]))
|
||||
res.Opaque = binary.BigEndian.Uint32(hdrBytes[12:16])
|
||||
res.Cas = binary.BigEndian.Uint64(hdrBytes[16:24])
|
||||
|
||||
bodyLen := int(binary.BigEndian.Uint32(hdrBytes[8:12])) - (klen + elen)
|
||||
|
||||
//defer function to debug the panic seen with MB-15557
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf(`Panic in Receive. Response %v \n
|
||||
key len %v extra len %v bodylen %v`, res, klen, elen, bodyLen)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := make([]byte, klen+elen+bodyLen)
|
||||
m, err := io.ReadFull(r, buf)
|
||||
if err == nil {
|
||||
res.Extras = buf[0:elen]
|
||||
res.Key = buf[elen : klen+elen]
|
||||
res.Body = buf[klen+elen:]
|
||||
}
|
||||
|
||||
return n + m, err
|
||||
}
|
||||
|
||||
type MCResponsePool struct {
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func NewMCResponsePool() *MCResponsePool {
|
||||
rv := &MCResponsePool{
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &MCResponse{}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func (this *MCResponsePool) Get() *MCResponse {
|
||||
return this.pool.Get().(*MCResponse)
|
||||
}
|
||||
|
||||
func (this *MCResponsePool) Put(r *MCResponse) {
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.Extras = nil
|
||||
r.Key = nil
|
||||
r.Body = nil
|
||||
r.Fatal = false
|
||||
|
||||
this.pool.Put(r)
|
||||
}
|
||||
|
||||
type StringMCResponsePool struct {
|
||||
pool *sync.Pool
|
||||
size int
|
||||
}
|
||||
|
||||
func NewStringMCResponsePool(size int) *StringMCResponsePool {
|
||||
rv := &StringMCResponsePool{
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make(map[string]*MCResponse, size)
|
||||
},
|
||||
},
|
||||
size: size,
|
||||
}
|
||||
|
||||
return rv
|
||||
}
|
||||
|
||||
func (this *StringMCResponsePool) Get() map[string]*MCResponse {
|
||||
return this.pool.Get().(map[string]*MCResponse)
|
||||
}
|
||||
|
||||
func (this *StringMCResponsePool) Put(m map[string]*MCResponse) {
|
||||
if m == nil || len(m) > 2*this.size {
|
||||
return
|
||||
}
|
||||
|
||||
for k := range m {
|
||||
m[k] = nil
|
||||
delete(m, k)
|
||||
}
|
||||
|
||||
this.pool.Put(m)
|
||||
}
|
168
vendor/github.com/couchbase/gomemcached/tap.go
generated
vendored
Normal file
168
vendor/github.com/couchbase/gomemcached/tap.go
generated
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
package gomemcached
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TapConnectFlag uint32
|
||||
|
||||
// Tap connect option flags
|
||||
const (
|
||||
BACKFILL = TapConnectFlag(0x01)
|
||||
DUMP = TapConnectFlag(0x02)
|
||||
LIST_VBUCKETS = TapConnectFlag(0x04)
|
||||
TAKEOVER_VBUCKETS = TapConnectFlag(0x08)
|
||||
SUPPORT_ACK = TapConnectFlag(0x10)
|
||||
REQUEST_KEYS_ONLY = TapConnectFlag(0x20)
|
||||
CHECKPOINT = TapConnectFlag(0x40)
|
||||
REGISTERED_CLIENT = TapConnectFlag(0x80)
|
||||
FIX_FLAG_BYTEORDER = TapConnectFlag(0x100)
|
||||
)
|
||||
|
||||
// Tap opaque event subtypes
|
||||
const (
|
||||
TAP_OPAQUE_ENABLE_AUTO_NACK = 0
|
||||
TAP_OPAQUE_INITIAL_VBUCKET_STREAM = 1
|
||||
TAP_OPAQUE_ENABLE_CHECKPOINT_SYNC = 2
|
||||
TAP_OPAQUE_CLOSE_TAP_STREAM = 7
|
||||
TAP_OPAQUE_CLOSE_BACKFILL = 8
|
||||
)
|
||||
|
||||
// Tap item flags
|
||||
const (
|
||||
TAP_ACK = 1
|
||||
TAP_NO_VALUE = 2
|
||||
TAP_FLAG_NETWORK_BYTE_ORDER = 4
|
||||
)
|
||||
|
||||
// TapConnectFlagNames for TapConnectFlag
|
||||
var TapConnectFlagNames = map[TapConnectFlag]string{
|
||||
BACKFILL: "BACKFILL",
|
||||
DUMP: "DUMP",
|
||||
LIST_VBUCKETS: "LIST_VBUCKETS",
|
||||
TAKEOVER_VBUCKETS: "TAKEOVER_VBUCKETS",
|
||||
SUPPORT_ACK: "SUPPORT_ACK",
|
||||
REQUEST_KEYS_ONLY: "REQUEST_KEYS_ONLY",
|
||||
CHECKPOINT: "CHECKPOINT",
|
||||
REGISTERED_CLIENT: "REGISTERED_CLIENT",
|
||||
FIX_FLAG_BYTEORDER: "FIX_FLAG_BYTEORDER",
|
||||
}
|
||||
|
||||
// TapItemParser is a function to parse a single tap extra.
|
||||
type TapItemParser func(io.Reader) (interface{}, error)
|
||||
|
||||
// TapParseUint64 is a function to parse a single tap uint64.
|
||||
func TapParseUint64(r io.Reader) (interface{}, error) {
|
||||
var rv uint64
|
||||
err := binary.Read(r, binary.BigEndian, &rv)
|
||||
return rv, err
|
||||
}
|
||||
|
||||
// TapParseUint16 is a function to parse a single tap uint16.
|
||||
func TapParseUint16(r io.Reader) (interface{}, error) {
|
||||
var rv uint16
|
||||
err := binary.Read(r, binary.BigEndian, &rv)
|
||||
return rv, err
|
||||
}
|
||||
|
||||
// TapParseBool is a function to parse a single tap boolean.
|
||||
func TapParseBool(r io.Reader) (interface{}, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// TapParseVBList parses a list of vBucket numbers as []uint16.
|
||||
func TapParseVBList(r io.Reader) (interface{}, error) {
|
||||
num, err := TapParseUint16(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n := int(num.(uint16))
|
||||
|
||||
rv := make([]uint16, n)
|
||||
for i := 0; i < n; i++ {
|
||||
x, err := TapParseUint16(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rv[i] = x.(uint16)
|
||||
}
|
||||
|
||||
return rv, err
|
||||
}
|
||||
|
||||
// TapFlagParsers parser functions for TAP fields.
|
||||
var TapFlagParsers = map[TapConnectFlag]TapItemParser{
|
||||
BACKFILL: TapParseUint64,
|
||||
LIST_VBUCKETS: TapParseVBList,
|
||||
}
|
||||
|
||||
// SplitFlags will split the ORed flags into the individual bit flags.
|
||||
func (f TapConnectFlag) SplitFlags() []TapConnectFlag {
|
||||
rv := []TapConnectFlag{}
|
||||
for i := uint32(1); f != 0; i = i << 1 {
|
||||
if uint32(f)&i == i {
|
||||
rv = append(rv, TapConnectFlag(i))
|
||||
}
|
||||
f = TapConnectFlag(uint32(f) & (^i))
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
func (f TapConnectFlag) String() string {
|
||||
parts := []string{}
|
||||
for _, x := range f.SplitFlags() {
|
||||
p := TapConnectFlagNames[x]
|
||||
if p == "" {
|
||||
p = fmt.Sprintf("0x%x", int(x))
|
||||
}
|
||||
parts = append(parts, p)
|
||||
}
|
||||
return strings.Join(parts, "|")
|
||||
}
|
||||
|
||||
type TapConnect struct {
|
||||
Flags map[TapConnectFlag]interface{}
|
||||
RemainingBody []byte
|
||||
Name string
|
||||
}
|
||||
|
||||
// ParseTapCommands parse the tap request into the interesting bits we may
|
||||
// need to do something with.
|
||||
func (req *MCRequest) ParseTapCommands() (TapConnect, error) {
|
||||
rv := TapConnect{
|
||||
Flags: map[TapConnectFlag]interface{}{},
|
||||
Name: string(req.Key),
|
||||
}
|
||||
|
||||
if len(req.Extras) < 4 {
|
||||
return rv, fmt.Errorf("not enough extra bytes: %x", req.Extras)
|
||||
}
|
||||
|
||||
flags := TapConnectFlag(binary.BigEndian.Uint32(req.Extras))
|
||||
|
||||
r := bytes.NewReader(req.Body)
|
||||
|
||||
for _, f := range flags.SplitFlags() {
|
||||
fun := TapFlagParsers[f]
|
||||
if fun == nil {
|
||||
fun = TapParseBool
|
||||
}
|
||||
|
||||
val, err := fun(r)
|
||||
if err != nil {
|
||||
return rv, err
|
||||
}
|
||||
|
||||
rv.Flags[f] = val
|
||||
}
|
||||
|
||||
var err error
|
||||
rv.RemainingBody, err = ioutil.ReadAll(r)
|
||||
|
||||
return rv, err
|
||||
}
|
47
vendor/github.com/couchbase/goutils/LICENSE.md
generated
vendored
Normal file
47
vendor/github.com/couchbase/goutils/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
COUCHBASE INC. COMMUNITY EDITION LICENSE AGREEMENT
|
||||
|
||||
IMPORTANT-READ CAREFULLY: BY CLICKING THE "I ACCEPT" BOX OR INSTALLING,
|
||||
DOWNLOADING OR OTHERWISE USING THIS SOFTWARE AND ANY ASSOCIATED
|
||||
DOCUMENTATION, YOU, ON BEHALF OF YOURSELF OR AS AN AUTHORIZED
|
||||
REPRESENTATIVE ON BEHALF OF AN ENTITY ("LICENSEE") AGREE TO ALL THE
|
||||
TERMS OF THIS COMMUNITY EDITION LICENSE AGREEMENT (THE "AGREEMENT")
|
||||
REGARDING YOUR USE OF THE SOFTWARE. YOU REPRESENT AND WARRANT THAT YOU
|
||||
HAVE FULL LEGAL AUTHORITY TO BIND THE LICENSEE TO THIS AGREEMENT. IF YOU
|
||||
DO NOT AGREE WITH ALL OF THESE TERMS, DO NOT SELECT THE "I ACCEPT" BOX
|
||||
AND DO NOT INSTALL, DOWNLOAD OR OTHERWISE USE THE SOFTWARE. THE
|
||||
EFFECTIVE DATE OF THIS AGREEMENT IS THE DATE ON WHICH YOU CLICK "I
|
||||
ACCEPT" OR OTHERWISE INSTALL, DOWNLOAD OR USE THE SOFTWARE.
|
||||
|
||||
1. License Grant. Couchbase Inc. hereby grants Licensee, free of charge,
|
||||
the non-exclusive right to use, copy, 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 Licensee including
|
||||
the following copyright notice in all copies or substantial portions of
|
||||
the Software:
|
||||
|
||||
Couchbase (r) http://www.Couchbase.com Copyright 2016 Couchbase, Inc.
|
||||
|
||||
As used in this Agreement, "Software" means the object code version of
|
||||
the applicable elastic data management server software provided by
|
||||
Couchbase Inc.
|
||||
|
||||
2. Restrictions. Licensee will not reverse engineer, disassemble, or
|
||||
decompile the Software (except to the extent such restrictions are
|
||||
prohibited by law).
|
||||
|
||||
3. Support. Couchbase, Inc. will provide Licensee with access to, and
|
||||
use of, the Couchbase, Inc. support forum available at the following
|
||||
URL: http://www.couchbase.org/forums/. Couchbase, Inc. may, at its
|
||||
discretion, modify, suspend or terminate support at any time upon notice
|
||||
to Licensee.
|
||||
|
||||
4. Warranty Disclaimer and Limitation of Liability. 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
|
||||
COUCHBASE INC. OR THE AUTHORS OR COPYRIGHT HOLDERS IN THE SOFTWARE BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES (IINCLUDING, WITHOUT LIMITATION, DIRECT,
|
||||
INDIRECT OR CONSEQUENTIAL 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.
|
481
vendor/github.com/couchbase/goutils/logging/logger.go
generated
vendored
Normal file
481
vendor/github.com/couchbase/goutils/logging/logger.go
generated
vendored
Normal file
@ -0,0 +1,481 @@
|
||||
// Copyright (c) 2016 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package logging
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Level int
|
||||
|
||||
const (
|
||||
NONE = Level(iota) // Disable all logging
|
||||
FATAL // System is in severe error state and has to abort
|
||||
SEVERE // System is in severe error state and cannot recover reliably
|
||||
ERROR // System is in error state but can recover and continue reliably
|
||||
WARN // System approaching error state, or is in a correct but undesirable state
|
||||
INFO // System-level events and status, in correct states
|
||||
REQUEST // Request-level events, with request-specific rlevel
|
||||
TRACE // Trace detailed system execution, e.g. function entry / exit
|
||||
DEBUG // Debug
|
||||
)
|
||||
|
||||
type LogEntryFormatter int
|
||||
|
||||
const (
|
||||
TEXTFORMATTER = LogEntryFormatter(iota)
|
||||
JSONFORMATTER
|
||||
KVFORMATTER
|
||||
)
|
||||
|
||||
func (level Level) String() string {
|
||||
return _LEVEL_NAMES[level]
|
||||
}
|
||||
|
||||
var _LEVEL_NAMES = []string{
|
||||
DEBUG: "DEBUG",
|
||||
TRACE: "TRACE",
|
||||
REQUEST: "REQUEST",
|
||||
INFO: "INFO",
|
||||
WARN: "WARN",
|
||||
ERROR: "ERROR",
|
||||
SEVERE: "SEVERE",
|
||||
FATAL: "FATAL",
|
||||
NONE: "NONE",
|
||||
}
|
||||
|
||||
var _LEVEL_MAP = map[string]Level{
|
||||
"debug": DEBUG,
|
||||
"trace": TRACE,
|
||||
"request": REQUEST,
|
||||
"info": INFO,
|
||||
"warn": WARN,
|
||||
"error": ERROR,
|
||||
"severe": SEVERE,
|
||||
"fatal": FATAL,
|
||||
"none": NONE,
|
||||
}
|
||||
|
||||
func ParseLevel(name string) (level Level, ok bool) {
|
||||
level, ok = _LEVEL_MAP[strings.ToLower(name)]
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Pair supports logging of key-value pairs. Keys beginning with _ are
|
||||
reserved for the logger, e.g. _time, _level, _msg, and _rlevel. The
|
||||
Pair APIs are designed to avoid heap allocation and garbage
|
||||
collection.
|
||||
|
||||
*/
|
||||
type Pairs []Pair
|
||||
type Pair struct {
|
||||
Name string
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Map allows key-value pairs to be specified using map literals or data
|
||||
structures. For example:
|
||||
|
||||
Errorm(msg, Map{...})
|
||||
|
||||
Map incurs heap allocation and garbage collection, so the Pair APIs
|
||||
should be preferred.
|
||||
|
||||
*/
|
||||
type Map map[string]interface{}
|
||||
|
||||
// Logger provides a common interface for logging libraries
|
||||
type Logger interface {
|
||||
/*
|
||||
These APIs write all the given pairs in addition to standard logger keys.
|
||||
*/
|
||||
Logp(level Level, msg string, kv ...Pair)
|
||||
|
||||
Debugp(msg string, kv ...Pair)
|
||||
|
||||
Tracep(msg string, kv ...Pair)
|
||||
|
||||
Requestp(rlevel Level, msg string, kv ...Pair)
|
||||
|
||||
Infop(msg string, kv ...Pair)
|
||||
|
||||
Warnp(msg string, kv ...Pair)
|
||||
|
||||
Errorp(msg string, kv ...Pair)
|
||||
|
||||
Severep(msg string, kv ...Pair)
|
||||
|
||||
Fatalp(msg string, kv ...Pair)
|
||||
|
||||
/*
|
||||
These APIs write the fields in the given kv Map in addition to standard logger keys.
|
||||
*/
|
||||
Logm(level Level, msg string, kv Map)
|
||||
|
||||
Debugm(msg string, kv Map)
|
||||
|
||||
Tracem(msg string, kv Map)
|
||||
|
||||
Requestm(rlevel Level, msg string, kv Map)
|
||||
|
||||
Infom(msg string, kv Map)
|
||||
|
||||
Warnm(msg string, kv Map)
|
||||
|
||||
Errorm(msg string, kv Map)
|
||||
|
||||
Severem(msg string, kv Map)
|
||||
|
||||
Fatalm(msg string, kv Map)
|
||||
|
||||
/*
|
||||
|
||||
These APIs only write _msg, _time, _level, and other logger keys. If
|
||||
the msg contains other fields, use the Pair or Map APIs instead.
|
||||
|
||||
*/
|
||||
Logf(level Level, fmt string, args ...interface{})
|
||||
|
||||
Debugf(fmt string, args ...interface{})
|
||||
|
||||
Tracef(fmt string, args ...interface{})
|
||||
|
||||
Requestf(rlevel Level, fmt string, args ...interface{})
|
||||
|
||||
Infof(fmt string, args ...interface{})
|
||||
|
||||
Warnf(fmt string, args ...interface{})
|
||||
|
||||
Errorf(fmt string, args ...interface{})
|
||||
|
||||
Severef(fmt string, args ...interface{})
|
||||
|
||||
Fatalf(fmt string, args ...interface{})
|
||||
|
||||
/*
|
||||
These APIs control the logging level
|
||||
*/
|
||||
|
||||
SetLevel(Level) // Set the logging level
|
||||
|
||||
Level() Level // Get the current logging level
|
||||
}
|
||||
|
||||
var logger Logger = nil
|
||||
var curLevel Level = DEBUG // initially set to never skip
|
||||
|
||||
var loggerMutex sync.RWMutex
|
||||
|
||||
// All the methods below first acquire the mutex (mostly in exclusive mode)
|
||||
// and only then check if logging at the current level is enabled.
|
||||
// This introduces a fair bottleneck for those log entries that should be
|
||||
// skipped (the majority, at INFO or below levels)
|
||||
// We try to predict here if we should lock the mutex at all by caching
|
||||
// the current log level: while dynamically changing logger, there might
|
||||
// be the odd entry skipped as the new level is cached.
|
||||
// Since we seem to never change the logger, this is not an issue.
|
||||
func skipLogging(level Level) bool {
|
||||
if logger == nil {
|
||||
return true
|
||||
}
|
||||
return level > curLevel
|
||||
}
|
||||
|
||||
func SetLogger(newLogger Logger) {
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger = newLogger
|
||||
if logger == nil {
|
||||
curLevel = NONE
|
||||
} else {
|
||||
curLevel = newLogger.Level()
|
||||
}
|
||||
}
|
||||
|
||||
func Logp(level Level, msg string, kv ...Pair) {
|
||||
if skipLogging(level) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Logp(level, msg, kv...)
|
||||
}
|
||||
|
||||
func Debugp(msg string, kv ...Pair) {
|
||||
if skipLogging(DEBUG) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Debugp(msg, kv...)
|
||||
}
|
||||
|
||||
func Tracep(msg string, kv ...Pair) {
|
||||
if skipLogging(TRACE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Tracep(msg, kv...)
|
||||
}
|
||||
|
||||
func Requestp(rlevel Level, msg string, kv ...Pair) {
|
||||
if skipLogging(REQUEST) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Requestp(rlevel, msg, kv...)
|
||||
}
|
||||
|
||||
func Infop(msg string, kv ...Pair) {
|
||||
if skipLogging(INFO) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Infop(msg, kv...)
|
||||
}
|
||||
|
||||
func Warnp(msg string, kv ...Pair) {
|
||||
if skipLogging(WARN) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Warnp(msg, kv...)
|
||||
}
|
||||
|
||||
func Errorp(msg string, kv ...Pair) {
|
||||
if skipLogging(ERROR) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Errorp(msg, kv...)
|
||||
}
|
||||
|
||||
func Severep(msg string, kv ...Pair) {
|
||||
if skipLogging(SEVERE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Severep(msg, kv...)
|
||||
}
|
||||
|
||||
func Fatalp(msg string, kv ...Pair) {
|
||||
if skipLogging(FATAL) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Fatalp(msg, kv...)
|
||||
}
|
||||
|
||||
func Logm(level Level, msg string, kv Map) {
|
||||
if skipLogging(level) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Logm(level, msg, kv)
|
||||
}
|
||||
|
||||
func Debugm(msg string, kv Map) {
|
||||
if skipLogging(DEBUG) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Debugm(msg, kv)
|
||||
}
|
||||
|
||||
func Tracem(msg string, kv Map) {
|
||||
if skipLogging(TRACE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Tracem(msg, kv)
|
||||
}
|
||||
|
||||
func Requestm(rlevel Level, msg string, kv Map) {
|
||||
if skipLogging(REQUEST) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Requestm(rlevel, msg, kv)
|
||||
}
|
||||
|
||||
func Infom(msg string, kv Map) {
|
||||
if skipLogging(INFO) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Infom(msg, kv)
|
||||
}
|
||||
|
||||
func Warnm(msg string, kv Map) {
|
||||
if skipLogging(WARN) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Warnm(msg, kv)
|
||||
}
|
||||
|
||||
func Errorm(msg string, kv Map) {
|
||||
if skipLogging(ERROR) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Errorm(msg, kv)
|
||||
}
|
||||
|
||||
func Severem(msg string, kv Map) {
|
||||
if skipLogging(SEVERE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Severem(msg, kv)
|
||||
}
|
||||
|
||||
func Fatalm(msg string, kv Map) {
|
||||
if skipLogging(FATAL) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Fatalm(msg, kv)
|
||||
}
|
||||
|
||||
func Logf(level Level, fmt string, args ...interface{}) {
|
||||
if skipLogging(level) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Logf(level, fmt, args...)
|
||||
}
|
||||
|
||||
func Debugf(fmt string, args ...interface{}) {
|
||||
if skipLogging(DEBUG) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Debugf(fmt, args...)
|
||||
}
|
||||
|
||||
func Tracef(fmt string, args ...interface{}) {
|
||||
if skipLogging(TRACE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Tracef(fmt, args...)
|
||||
}
|
||||
|
||||
func Requestf(rlevel Level, fmt string, args ...interface{}) {
|
||||
if skipLogging(REQUEST) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Requestf(rlevel, fmt, args...)
|
||||
}
|
||||
|
||||
func Infof(fmt string, args ...interface{}) {
|
||||
if skipLogging(INFO) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Infof(fmt, args...)
|
||||
}
|
||||
|
||||
func Warnf(fmt string, args ...interface{}) {
|
||||
if skipLogging(WARN) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Warnf(fmt, args...)
|
||||
}
|
||||
|
||||
func Errorf(fmt string, args ...interface{}) {
|
||||
if skipLogging(ERROR) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Errorf(fmt, args...)
|
||||
}
|
||||
|
||||
func Severef(fmt string, args ...interface{}) {
|
||||
if skipLogging(SEVERE) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Severef(fmt, args...)
|
||||
}
|
||||
|
||||
func Fatalf(fmt string, args ...interface{}) {
|
||||
if skipLogging(FATAL) {
|
||||
return
|
||||
}
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Fatalf(fmt, args...)
|
||||
}
|
||||
|
||||
func SetLevel(level Level) {
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.SetLevel(level)
|
||||
curLevel = level
|
||||
}
|
||||
|
||||
func LogLevel() Level {
|
||||
loggerMutex.RLock()
|
||||
defer loggerMutex.RUnlock()
|
||||
return logger.Level()
|
||||
}
|
||||
|
||||
func Stackf(level Level, fmt string, args ...interface{}) {
|
||||
if skipLogging(level) {
|
||||
return
|
||||
}
|
||||
buf := make([]byte, 1<<16)
|
||||
n := runtime.Stack(buf, false)
|
||||
s := string(buf[0:n])
|
||||
loggerMutex.Lock()
|
||||
defer loggerMutex.Unlock()
|
||||
logger.Logf(level, fmt, args...)
|
||||
logger.Logf(level, s)
|
||||
}
|
||||
|
||||
func init() {
|
||||
logger = NewLogger(os.Stderr, INFO, TEXTFORMATTER)
|
||||
SetLogger(logger)
|
||||
}
|
318
vendor/github.com/couchbase/goutils/logging/logger_golog.go
generated
vendored
Normal file
318
vendor/github.com/couchbase/goutils/logging/logger_golog.go
generated
vendored
Normal file
@ -0,0 +1,318 @@
|
||||
// Copyright (c) 2016 Couchbase, Inc.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
|
||||
// except in compliance with the License. You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// Unless required by applicable law or agreed to in writing, software distributed under the
|
||||
// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
// either express or implied. See the License for the specific language governing permissions
|
||||
// and limitations under the License.
|
||||
|
||||
package logging
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type goLogger struct {
|
||||
logger *log.Logger
|
||||
level Level
|
||||
entryFormatter formatter
|
||||
}
|
||||
|
||||
const (
|
||||
_LEVEL = "_level"
|
||||
_MSG = "_msg"
|
||||
_TIME = "_time"
|
||||
_RLEVEL = "_rlevel"
|
||||
)
|
||||
|
||||
func NewLogger(out io.Writer, lvl Level, fmtLogging LogEntryFormatter) *goLogger {
|
||||
logger := &goLogger{
|
||||
logger: log.New(out, "", 0),
|
||||
level: lvl,
|
||||
}
|
||||
if fmtLogging == JSONFORMATTER {
|
||||
logger.entryFormatter = &jsonFormatter{}
|
||||
} else if fmtLogging == KVFORMATTER {
|
||||
logger.entryFormatter = &keyvalueFormatter{}
|
||||
} else {
|
||||
logger.entryFormatter = &textFormatter{}
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
func (gl *goLogger) Logp(level Level, msg string, kv ...Pair) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if level <= gl.level {
|
||||
e := newLogEntry(msg, level)
|
||||
copyPairs(e, kv)
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Debugp(msg string, kv ...Pair) {
|
||||
gl.Logp(DEBUG, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Tracep(msg string, kv ...Pair) {
|
||||
gl.Logp(TRACE, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Requestp(rlevel Level, msg string, kv ...Pair) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if REQUEST <= gl.level {
|
||||
e := newLogEntry(msg, REQUEST)
|
||||
e.Rlevel = rlevel
|
||||
copyPairs(e, kv)
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Infop(msg string, kv ...Pair) {
|
||||
gl.Logp(INFO, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Warnp(msg string, kv ...Pair) {
|
||||
gl.Logp(WARN, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Errorp(msg string, kv ...Pair) {
|
||||
gl.Logp(ERROR, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Severep(msg string, kv ...Pair) {
|
||||
gl.Logp(SEVERE, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Fatalp(msg string, kv ...Pair) {
|
||||
gl.Logp(FATAL, msg, kv...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Logm(level Level, msg string, kv Map) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if level <= gl.level {
|
||||
e := newLogEntry(msg, level)
|
||||
e.Data = kv
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Debugm(msg string, kv Map) {
|
||||
gl.Logm(DEBUG, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Tracem(msg string, kv Map) {
|
||||
gl.Logm(TRACE, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Requestm(rlevel Level, msg string, kv Map) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if REQUEST <= gl.level {
|
||||
e := newLogEntry(msg, REQUEST)
|
||||
e.Rlevel = rlevel
|
||||
e.Data = kv
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Infom(msg string, kv Map) {
|
||||
gl.Logm(INFO, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Warnm(msg string, kv Map) {
|
||||
gl.Logm(WARN, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Errorm(msg string, kv Map) {
|
||||
gl.Logm(ERROR, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Severem(msg string, kv Map) {
|
||||
gl.Logm(SEVERE, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Fatalm(msg string, kv Map) {
|
||||
gl.Logm(FATAL, msg, kv)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Logf(level Level, format string, args ...interface{}) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if level <= gl.level {
|
||||
e := newLogEntry(fmt.Sprintf(format, args...), level)
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Debugf(format string, args ...interface{}) {
|
||||
gl.Logf(DEBUG, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Tracef(format string, args ...interface{}) {
|
||||
gl.Logf(TRACE, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Requestf(rlevel Level, format string, args ...interface{}) {
|
||||
if gl.logger == nil {
|
||||
return
|
||||
}
|
||||
if REQUEST <= gl.level {
|
||||
e := newLogEntry(fmt.Sprintf(format, args...), REQUEST)
|
||||
e.Rlevel = rlevel
|
||||
gl.log(e)
|
||||
}
|
||||
}
|
||||
|
||||
func (gl *goLogger) Infof(format string, args ...interface{}) {
|
||||
gl.Logf(INFO, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Warnf(format string, args ...interface{}) {
|
||||
gl.Logf(WARN, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Errorf(format string, args ...interface{}) {
|
||||
gl.Logf(ERROR, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Severef(format string, args ...interface{}) {
|
||||
gl.Logf(SEVERE, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Fatalf(format string, args ...interface{}) {
|
||||
gl.Logf(FATAL, format, args...)
|
||||
}
|
||||
|
||||
func (gl *goLogger) Level() Level {
|
||||
return gl.level
|
||||
}
|
||||
|
||||
func (gl *goLogger) SetLevel(level Level) {
|
||||
gl.level = level
|
||||
}
|
||||
|
||||
func (gl *goLogger) log(newEntry *logEntry) {
|
||||
s := gl.entryFormatter.format(newEntry)
|
||||
gl.logger.Print(s)
|
||||
}
|
||||
|
||||
type logEntry struct {
|
||||
Time string
|
||||
Level Level
|
||||
Rlevel Level
|
||||
Message string
|
||||
Data Map
|
||||
}
|
||||
|
||||
func newLogEntry(msg string, level Level) *logEntry {
|
||||
return &logEntry{
|
||||
Time: time.Now().Format("2006-01-02T15:04:05.000-07:00"), // time.RFC3339 with milliseconds
|
||||
Level: level,
|
||||
Rlevel: NONE,
|
||||
Message: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func copyPairs(newEntry *logEntry, pairs []Pair) {
|
||||
newEntry.Data = make(Map, len(pairs))
|
||||
for _, p := range pairs {
|
||||
newEntry.Data[p.Name] = p.Value
|
||||
}
|
||||
}
|
||||
|
||||
type formatter interface {
|
||||
format(*logEntry) string
|
||||
}
|
||||
|
||||
type textFormatter struct {
|
||||
}
|
||||
|
||||
// ex. 2016-02-10T09:15:25.498-08:00 [INFO] This is a message from test in text format
|
||||
|
||||
func (*textFormatter) format(newEntry *logEntry) string {
|
||||
b := &bytes.Buffer{}
|
||||
appendValue(b, newEntry.Time)
|
||||
if newEntry.Rlevel != NONE {
|
||||
fmt.Fprintf(b, "[%s,%s] ", newEntry.Level.String(), newEntry.Rlevel.String())
|
||||
} else {
|
||||
fmt.Fprintf(b, "[%s] ", newEntry.Level.String())
|
||||
}
|
||||
appendValue(b, newEntry.Message)
|
||||
for key, value := range newEntry.Data {
|
||||
appendKeyValue(b, key, value)
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
s := bytes.NewBuffer(b.Bytes())
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func appendValue(b *bytes.Buffer, value interface{}) {
|
||||
if _, ok := value.(string); ok {
|
||||
fmt.Fprintf(b, "%s ", value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v ", value)
|
||||
}
|
||||
}
|
||||
|
||||
type keyvalueFormatter struct {
|
||||
}
|
||||
|
||||
// ex. _time=2016-02-10T09:15:25.498-08:00 _level=INFO _msg=This is a message from test in key-value format
|
||||
|
||||
func (*keyvalueFormatter) format(newEntry *logEntry) string {
|
||||
b := &bytes.Buffer{}
|
||||
appendKeyValue(b, _TIME, newEntry.Time)
|
||||
appendKeyValue(b, _LEVEL, newEntry.Level.String())
|
||||
if newEntry.Rlevel != NONE {
|
||||
appendKeyValue(b, _RLEVEL, newEntry.Rlevel.String())
|
||||
}
|
||||
appendKeyValue(b, _MSG, newEntry.Message)
|
||||
for key, value := range newEntry.Data {
|
||||
appendKeyValue(b, key, value)
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
s := bytes.NewBuffer(b.Bytes())
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func appendKeyValue(b *bytes.Buffer, key, value interface{}) {
|
||||
if _, ok := value.(string); ok {
|
||||
fmt.Fprintf(b, "%v=%s ", key, value)
|
||||
} else {
|
||||
fmt.Fprintf(b, "%v=%v ", key, value)
|
||||
}
|
||||
}
|
||||
|
||||
type jsonFormatter struct {
|
||||
}
|
||||
|
||||
// ex. {"_level":"INFO","_msg":"This is a message from test in json format","_time":"2016-02-10T09:12:59.518-08:00"}
|
||||
|
||||
func (*jsonFormatter) format(newEntry *logEntry) string {
|
||||
if newEntry.Data == nil {
|
||||
newEntry.Data = make(Map, 5)
|
||||
}
|
||||
newEntry.Data[_TIME] = newEntry.Time
|
||||
newEntry.Data[_LEVEL] = newEntry.Level.String()
|
||||
if newEntry.Rlevel != NONE {
|
||||
newEntry.Data[_RLEVEL] = newEntry.Rlevel.String()
|
||||
}
|
||||
newEntry.Data[_MSG] = newEntry.Message
|
||||
serialized, _ := json.Marshal(newEntry.Data)
|
||||
s := bytes.NewBuffer(append(serialized, '\n'))
|
||||
return s.String()
|
||||
}
|
207
vendor/github.com/couchbase/goutils/scramsha/scramsha.go
generated
vendored
Normal file
207
vendor/github.com/couchbase/goutils/scramsha/scramsha.go
generated
vendored
Normal file
@ -0,0 +1,207 @@
|
||||
// @author Couchbase <info@couchbase.com>
|
||||
// @copyright 2018 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package scramsha provides implementation of client side SCRAM-SHA
|
||||
// according to https://tools.ietf.org/html/rfc5802
|
||||
package scramsha
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"hash"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func hmacHash(message []byte, secret []byte, hashFunc func() hash.Hash) []byte {
|
||||
h := hmac.New(hashFunc, secret)
|
||||
h.Write(message)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func shaHash(message []byte, hashFunc func() hash.Hash) []byte {
|
||||
h := hashFunc()
|
||||
h.Write(message)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func generateClientNonce(size int) (string, error) {
|
||||
randomBytes := make([]byte, size)
|
||||
_, err := rand.Read(randomBytes)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Unable to generate nonce")
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(randomBytes), nil
|
||||
}
|
||||
|
||||
// ScramSha provides context for SCRAM-SHA handling
|
||||
type ScramSha struct {
|
||||
hashSize int
|
||||
hashFunc func() hash.Hash
|
||||
clientNonce string
|
||||
serverNonce string
|
||||
salt []byte
|
||||
i int
|
||||
saltedPassword []byte
|
||||
authMessage string
|
||||
}
|
||||
|
||||
var knownMethods = []string{"SCRAM-SHA512", "SCRAM-SHA256", "SCRAM-SHA1"}
|
||||
|
||||
// BestMethod returns SCRAM-SHA method we consider the best out of suggested
|
||||
// by server
|
||||
func BestMethod(methods string) (string, error) {
|
||||
for _, m := range knownMethods {
|
||||
if strings.Index(methods, m) != -1 {
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return "", errors.Errorf(
|
||||
"None of the server suggested methods [%s] are supported",
|
||||
methods)
|
||||
}
|
||||
|
||||
// NewScramSha creates context for SCRAM-SHA handling
|
||||
func NewScramSha(method string) (*ScramSha, error) {
|
||||
s := &ScramSha{}
|
||||
|
||||
if method == knownMethods[0] {
|
||||
s.hashFunc = sha512.New
|
||||
s.hashSize = 64
|
||||
} else if method == knownMethods[1] {
|
||||
s.hashFunc = sha256.New
|
||||
s.hashSize = 32
|
||||
} else if method == knownMethods[2] {
|
||||
s.hashFunc = sha1.New
|
||||
s.hashSize = 20
|
||||
} else {
|
||||
return nil, errors.Errorf("Unsupported method %s", method)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetStartRequest builds start SCRAM-SHA request to be sent to server
|
||||
func (s *ScramSha) GetStartRequest(user string) (string, error) {
|
||||
var err error
|
||||
s.clientNonce, err = generateClientNonce(24)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "Unable to generate SCRAM-SHA "+
|
||||
"start request for user %s", user)
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("n,,n=%s,r=%s", user, s.clientNonce)
|
||||
s.authMessage = message[3:]
|
||||
return message, nil
|
||||
}
|
||||
|
||||
// HandleStartResponse handles server response on start SCRAM-SHA request
|
||||
func (s *ScramSha) HandleStartResponse(response string) error {
|
||||
parts := strings.Split(response, ",")
|
||||
if len(parts) != 3 {
|
||||
return errors.Errorf("expected 3 fields in first SCRAM-SHA-1 "+
|
||||
"server message %s", response)
|
||||
}
|
||||
if !strings.HasPrefix(parts[0], "r=") || len(parts[0]) < 3 {
|
||||
return errors.Errorf("Server sent an invalid nonce %s",
|
||||
parts[0])
|
||||
}
|
||||
if !strings.HasPrefix(parts[1], "s=") || len(parts[1]) < 3 {
|
||||
return errors.Errorf("Server sent an invalid salt %s", parts[1])
|
||||
}
|
||||
if !strings.HasPrefix(parts[2], "i=") || len(parts[2]) < 3 {
|
||||
return errors.Errorf("Server sent an invalid iteration count %s",
|
||||
parts[2])
|
||||
}
|
||||
|
||||
s.serverNonce = parts[0][2:]
|
||||
encodedSalt := parts[1][2:]
|
||||
var err error
|
||||
s.i, err = strconv.Atoi(parts[2][2:])
|
||||
if err != nil {
|
||||
return errors.Errorf("Iteration count %s must be integer.",
|
||||
parts[2][2:])
|
||||
}
|
||||
|
||||
if s.i < 1 {
|
||||
return errors.New("Iteration count should be positive")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(s.serverNonce, s.clientNonce) {
|
||||
return errors.Errorf("Server nonce %s doesn't contain client"+
|
||||
" nonce %s", s.serverNonce, s.clientNonce)
|
||||
}
|
||||
|
||||
s.salt, err = base64.StdEncoding.DecodeString(encodedSalt)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to decode salt %s",
|
||||
encodedSalt)
|
||||
}
|
||||
|
||||
s.authMessage = s.authMessage + "," + response
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFinalRequest builds final SCRAM-SHA request to be sent to server
|
||||
func (s *ScramSha) GetFinalRequest(pass string) string {
|
||||
clientFinalMessageBare := "c=biws,r=" + s.serverNonce
|
||||
s.authMessage = s.authMessage + "," + clientFinalMessageBare
|
||||
|
||||
s.saltedPassword = pbkdf2.Key([]byte(pass), s.salt, s.i,
|
||||
s.hashSize, s.hashFunc)
|
||||
|
||||
clientKey := hmacHash([]byte("Client Key"), s.saltedPassword, s.hashFunc)
|
||||
storedKey := shaHash(clientKey, s.hashFunc)
|
||||
clientSignature := hmacHash([]byte(s.authMessage), storedKey, s.hashFunc)
|
||||
|
||||
clientProof := make([]byte, len(clientSignature))
|
||||
for i := 0; i < len(clientSignature); i++ {
|
||||
clientProof[i] = clientKey[i] ^ clientSignature[i]
|
||||
}
|
||||
|
||||
return clientFinalMessageBare + ",p=" +
|
||||
base64.StdEncoding.EncodeToString(clientProof)
|
||||
}
|
||||
|
||||
// HandleFinalResponse handles server's response on final SCRAM-SHA request
|
||||
func (s *ScramSha) HandleFinalResponse(response string) error {
|
||||
if strings.Contains(response, ",") ||
|
||||
!strings.HasPrefix(response, "v=") {
|
||||
return errors.Errorf("Server sent an invalid final message %s",
|
||||
response)
|
||||
}
|
||||
|
||||
decodedMessage, err := base64.StdEncoding.DecodeString(response[2:])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Unable to decode server message %s",
|
||||
response[2:])
|
||||
}
|
||||
serverKey := hmacHash([]byte("Server Key"), s.saltedPassword,
|
||||
s.hashFunc)
|
||||
serverSignature := hmacHash([]byte(s.authMessage), serverKey,
|
||||
s.hashFunc)
|
||||
if string(decodedMessage) != string(serverSignature) {
|
||||
return errors.Errorf("Server proof %s doesn't match "+
|
||||
"the expected: %s",
|
||||
string(decodedMessage), string(serverSignature))
|
||||
}
|
||||
return nil
|
||||
}
|
252
vendor/github.com/couchbase/goutils/scramsha/scramsha_http.go
generated
vendored
Normal file
252
vendor/github.com/couchbase/goutils/scramsha/scramsha_http.go
generated
vendored
Normal file
@ -0,0 +1,252 @@
|
||||
// @author Couchbase <info@couchbase.com>
|
||||
// @copyright 2018 Couchbase, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package scramsha provides implementation of client side SCRAM-SHA
|
||||
// via Http according to https://tools.ietf.org/html/rfc7804
|
||||
package scramsha
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// consts used to parse scramsha response from target
|
||||
const (
|
||||
WWWAuthenticate = "WWW-Authenticate"
|
||||
AuthenticationInfo = "Authentication-Info"
|
||||
Authorization = "Authorization"
|
||||
DataPrefix = "data="
|
||||
SidPrefix = "sid="
|
||||
)
|
||||
|
||||
// Request provides implementation of http request that can be retried
|
||||
type Request struct {
|
||||
body io.ReadSeeker
|
||||
|
||||
// Embed an HTTP request directly. This makes a *Request act exactly
|
||||
// like an *http.Request so that all meta methods are supported.
|
||||
*http.Request
|
||||
}
|
||||
|
||||
type lenReader interface {
|
||||
Len() int
|
||||
}
|
||||
|
||||
// NewRequest creates http request that can be retried
|
||||
func NewRequest(method, url string, body io.ReadSeeker) (*Request, error) {
|
||||
// Wrap the body in a noop ReadCloser if non-nil. This prevents the
|
||||
// reader from being closed by the HTTP client.
|
||||
var rcBody io.ReadCloser
|
||||
if body != nil {
|
||||
rcBody = ioutil.NopCloser(body)
|
||||
}
|
||||
|
||||
// Make the request with the noop-closer for the body.
|
||||
httpReq, err := http.NewRequest(method, url, rcBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if we can set the Content-Length automatically.
|
||||
if lr, ok := body.(lenReader); ok {
|
||||
httpReq.ContentLength = int64(lr.Len())
|
||||
}
|
||||
|
||||
return &Request{body, httpReq}, nil
|
||||
}
|
||||
|
||||
func encode(str string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
func decode(str string) (string, error) {
|
||||
bytes, err := base64.StdEncoding.DecodeString(str)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("Cannot base64 decode %s",
|
||||
str)
|
||||
}
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
func trimPrefix(s, prefix string) (string, error) {
|
||||
l := len(s)
|
||||
trimmed := strings.TrimPrefix(s, prefix)
|
||||
if l == len(trimmed) {
|
||||
return trimmed, errors.Errorf("Prefix %s not found in %s",
|
||||
prefix, s)
|
||||
}
|
||||
return trimmed, nil
|
||||
}
|
||||
|
||||
func drainBody(resp *http.Response) {
|
||||
defer resp.Body.Close()
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
}
|
||||
|
||||
// DoScramSha performs SCRAM-SHA handshake via Http
|
||||
func DoScramSha(req *Request,
|
||||
username string,
|
||||
password string,
|
||||
client *http.Client) (*http.Response, error) {
|
||||
|
||||
method := "SCRAM-SHA-512"
|
||||
s, err := NewScramSha("SCRAM-SHA512")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err,
|
||||
"Unable to initialize SCRAM-SHA handler")
|
||||
}
|
||||
|
||||
message, err := s.GetStartRequest(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encodedMessage := method + " " + DataPrefix + encode(message)
|
||||
|
||||
req.Header.Set(Authorization, encodedMessage)
|
||||
|
||||
res, err := client.Do(req.Request)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Problem sending SCRAM-SHA start"+
|
||||
"request")
|
||||
}
|
||||
|
||||
if res.StatusCode != http.StatusUnauthorized {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
authHeader := res.Header.Get(WWWAuthenticate)
|
||||
if authHeader == "" {
|
||||
drainBody(res)
|
||||
return nil, errors.Errorf("Header %s is not populated in "+
|
||||
"SCRAM-SHA start response", WWWAuthenticate)
|
||||
}
|
||||
|
||||
authHeader, err = trimPrefix(authHeader, method+" ")
|
||||
if err != nil {
|
||||
if strings.HasPrefix(authHeader, "Basic ") {
|
||||
// user not found
|
||||
return res, nil
|
||||
}
|
||||
drainBody(res)
|
||||
return nil, errors.Wrapf(err, "Error while parsing SCRAM-SHA "+
|
||||
"start response %s", authHeader)
|
||||
}
|
||||
|
||||
drainBody(res)
|
||||
|
||||
sid, response, err := parseSidAndData(authHeader)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error while parsing SCRAM-SHA "+
|
||||
"start response %s", authHeader)
|
||||
}
|
||||
|
||||
err = s.HandleStartResponse(response)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Error parsing SCRAM-SHA start "+
|
||||
"response %s", response)
|
||||
}
|
||||
|
||||
message = s.GetFinalRequest(password)
|
||||
encodedMessage = method + " " + SidPrefix + sid + "," + DataPrefix +
|
||||
encode(message)
|
||||
|
||||
req.Header.Set(Authorization, encodedMessage)
|
||||
|
||||
// rewind request body so it can be resent again
|
||||
if req.body != nil {
|
||||
if _, err = req.body.Seek(0, 0); err != nil {
|
||||
return nil, errors.Errorf("Failed to seek body: %v",
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
res, err = client.Do(req.Request)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Problem sending SCRAM-SHA final"+
|
||||
"request")
|
||||
}
|
||||
|
||||
if res.StatusCode == http.StatusUnauthorized {
|
||||
// TODO retrieve and return error
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if res.StatusCode >= http.StatusInternalServerError {
|
||||
// in this case we cannot expect server to set headers properly
|
||||
return res, nil
|
||||
}
|
||||
|
||||
authHeader = res.Header.Get(AuthenticationInfo)
|
||||
if authHeader == "" {
|
||||
drainBody(res)
|
||||
return nil, errors.Errorf("Header %s is not populated in "+
|
||||
"SCRAM-SHA final response", AuthenticationInfo)
|
||||
}
|
||||
|
||||
finalSid, response, err := parseSidAndData(authHeader)
|
||||
if err != nil {
|
||||
drainBody(res)
|
||||
return nil, errors.Wrapf(err, "Error while parsing SCRAM-SHA "+
|
||||
"final response %s", authHeader)
|
||||
}
|
||||
|
||||
if finalSid != sid {
|
||||
drainBody(res)
|
||||
return nil, errors.Errorf("Sid %s returned by server "+
|
||||
"doesn't match the original sid %s", finalSid, sid)
|
||||
}
|
||||
|
||||
err = s.HandleFinalResponse(response)
|
||||
if err != nil {
|
||||
drainBody(res)
|
||||
return nil, errors.Wrapf(err,
|
||||
"Error handling SCRAM-SHA final server response %s",
|
||||
response)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func parseSidAndData(authHeader string) (string, string, error) {
|
||||
sidIndex := strings.Index(authHeader, SidPrefix)
|
||||
if sidIndex < 0 {
|
||||
return "", "", errors.Errorf("Cannot find %s in %s",
|
||||
SidPrefix, authHeader)
|
||||
}
|
||||
|
||||
sidEndIndex := strings.Index(authHeader, ",")
|
||||
if sidEndIndex < 0 {
|
||||
return "", "", errors.Errorf("Cannot find ',' in %s",
|
||||
authHeader)
|
||||
}
|
||||
|
||||
sid := authHeader[sidIndex+len(SidPrefix) : sidEndIndex]
|
||||
|
||||
dataIndex := strings.Index(authHeader, DataPrefix)
|
||||
if dataIndex < 0 {
|
||||
return "", "", errors.Errorf("Cannot find %s in %s",
|
||||
DataPrefix, authHeader)
|
||||
}
|
||||
|
||||
data, err := decode(authHeader[dataIndex+len(DataPrefix):])
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return sid, data, nil
|
||||
}
|
19
vendor/github.com/couchbaselabs/go-couchbase/LICENSE
generated
vendored
Normal file
19
vendor/github.com/couchbaselabs/go-couchbase/LICENSE
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2013 Couchbase, Inc.
|
||||
|
||||
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.
|
32
vendor/github.com/couchbaselabs/go-couchbase/audit.go
generated
vendored
Normal file
32
vendor/github.com/couchbaselabs/go-couchbase/audit.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
package couchbase
|
||||
|
||||
import ()
|
||||
|
||||
// Sample data:
|
||||
// {"disabled":["12333", "22244"],"uid":"132492431","auditdEnabled":true,
|
||||
// "disabledUsers":[{"name":"bill","domain":"local"},{"name":"bob","domain":"local"}],
|
||||
// "logPath":"/Users/johanlarson/Library/Application Support/Couchbase/var/lib/couchbase/logs",
|
||||
// "rotateInterval":86400,"rotateSize":20971520}
|
||||
type AuditSpec struct {
|
||||
Disabled []uint32 `json:"disabled"`
|
||||
Uid string `json:"uid"`
|
||||
AuditdEnabled bool `json:"auditdEnabled`
|
||||
DisabledUsers []AuditUser `json:"disabledUsers"`
|
||||
LogPath string `json:"logPath"`
|
||||
RotateInterval int64 `json:"rotateInterval"`
|
||||
RotateSize int64 `json:"rotateSize"`
|
||||
}
|
||||
|
||||
type AuditUser struct {
|
||||
Name string `json:"name"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
func (c *Client) GetAuditSpec() (*AuditSpec, error) {
|
||||
ret := &AuditSpec{}
|
||||
err := c.parseURLResponse("/settings/audit", ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
1385
vendor/github.com/couchbaselabs/go-couchbase/client.go
generated
vendored
Normal file
1385
vendor/github.com/couchbaselabs/go-couchbase/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
387
vendor/github.com/couchbaselabs/go-couchbase/conn_pool.go
generated
vendored
Normal file
387
vendor/github.com/couchbaselabs/go-couchbase/conn_pool.go
generated
vendored
Normal file
@ -0,0 +1,387 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/couchbase/gomemcached"
|
||||
"github.com/couchbase/gomemcached/client"
|
||||
"github.com/couchbase/goutils/logging"
|
||||
)
|
||||
|
||||
// GenericMcdAuthHandler is a kind of AuthHandler that performs
|
||||
// special auth exchange (like non-standard auth, possibly followed by
|
||||
// select-bucket).
|
||||
type GenericMcdAuthHandler interface {
|
||||
AuthHandler
|
||||
AuthenticateMemcachedConn(host string, conn *memcached.Client) error
|
||||
}
|
||||
|
||||
// Error raised when a connection can't be retrieved from a pool.
|
||||
var TimeoutError = errors.New("timeout waiting to build connection")
|
||||
var errClosedPool = errors.New("the connection pool is closed")
|
||||
var errNoPool = errors.New("no connection pool")
|
||||
|
||||
// Default timeout for retrieving a connection from the pool.
|
||||
var ConnPoolTimeout = time.Hour * 24 * 30
|
||||
|
||||
// overflow connection closer cycle time
|
||||
var ConnCloserInterval = time.Second * 30
|
||||
|
||||
// ConnPoolAvailWaitTime is the amount of time to wait for an existing
|
||||
// connection from the pool before considering the creation of a new
|
||||
// one.
|
||||
var ConnPoolAvailWaitTime = time.Millisecond
|
||||
|
||||
type connectionPool struct {
|
||||
host string
|
||||
mkConn func(host string, ah AuthHandler) (*memcached.Client, error)
|
||||
auth AuthHandler
|
||||
connections chan *memcached.Client
|
||||
createsem chan bool
|
||||
bailOut chan bool
|
||||
poolSize int
|
||||
connCount uint64
|
||||
inUse bool
|
||||
}
|
||||
|
||||
func newConnectionPool(host string, ah AuthHandler, closer bool, poolSize, poolOverflow int) *connectionPool {
|
||||
connSize := poolSize
|
||||
if closer {
|
||||
connSize += poolOverflow
|
||||
}
|
||||
rv := &connectionPool{
|
||||
host: host,
|
||||
connections: make(chan *memcached.Client, connSize),
|
||||
createsem: make(chan bool, poolSize+poolOverflow),
|
||||
mkConn: defaultMkConn,
|
||||
auth: ah,
|
||||
poolSize: poolSize,
|
||||
}
|
||||
if closer {
|
||||
rv.bailOut = make(chan bool, 1)
|
||||
go rv.connCloser()
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// ConnPoolTimeout is notified whenever connections are acquired from a pool.
|
||||
var ConnPoolCallback func(host string, source string, start time.Time, err error)
|
||||
|
||||
func defaultMkConn(host string, ah AuthHandler) (*memcached.Client, error) {
|
||||
var features memcached.Features
|
||||
|
||||
conn, err := memcached.Connect("tcp", host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if TCPKeepalive == true {
|
||||
conn.SetKeepAliveOptions(time.Duration(TCPKeepaliveInterval) * time.Second)
|
||||
}
|
||||
|
||||
if EnableMutationToken == true {
|
||||
features = append(features, memcached.FeatureMutationToken)
|
||||
}
|
||||
if EnableDataType == true {
|
||||
features = append(features, memcached.FeatureDataType)
|
||||
}
|
||||
|
||||
if EnableXattr == true {
|
||||
features = append(features, memcached.FeatureXattr)
|
||||
}
|
||||
|
||||
if len(features) > 0 {
|
||||
if DefaultTimeout > 0 {
|
||||
conn.SetDeadline(getDeadline(noDeadline, DefaultTimeout))
|
||||
}
|
||||
|
||||
res, err := conn.EnableFeatures(features)
|
||||
|
||||
if DefaultTimeout > 0 {
|
||||
conn.SetDeadline(noDeadline)
|
||||
}
|
||||
|
||||
if err != nil && isTimeoutError(err) {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err != nil || res.Status != gomemcached.SUCCESS {
|
||||
logging.Warnf("Unable to enable features %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if gah, ok := ah.(GenericMcdAuthHandler); ok {
|
||||
err = gah.AuthenticateMemcachedConn(host, conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
name, pass, bucket := ah.GetCredentials()
|
||||
if name != "default" {
|
||||
_, err = conn.Auth(name, pass)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Select bucket (Required for cb_auth creds)
|
||||
// Required when doing auth with _admin credentials
|
||||
if bucket != "" && bucket != name {
|
||||
_, err = conn.SelectBucket(bucket)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (cp *connectionPool) Close() (err error) {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
err = errors.New("connectionPool.Close error")
|
||||
}
|
||||
}()
|
||||
if cp.bailOut != nil {
|
||||
|
||||
// defensively, we won't wait if the channel is full
|
||||
select {
|
||||
case cp.bailOut <- false:
|
||||
default:
|
||||
}
|
||||
}
|
||||
close(cp.connections)
|
||||
for c := range cp.connections {
|
||||
c.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (cp *connectionPool) Node() string {
|
||||
return cp.host
|
||||
}
|
||||
|
||||
func (cp *connectionPool) GetWithTimeout(d time.Duration) (rv *memcached.Client, err error) {
|
||||
if cp == nil {
|
||||
return nil, errNoPool
|
||||
}
|
||||
|
||||
path := ""
|
||||
|
||||
if ConnPoolCallback != nil {
|
||||
defer func(path *string, start time.Time) {
|
||||
ConnPoolCallback(cp.host, *path, start, err)
|
||||
}(&path, time.Now())
|
||||
}
|
||||
|
||||
path = "short-circuit"
|
||||
|
||||
// short-circuit available connetions.
|
||||
select {
|
||||
case rv, isopen := <-cp.connections:
|
||||
if !isopen {
|
||||
return nil, errClosedPool
|
||||
}
|
||||
atomic.AddUint64(&cp.connCount, 1)
|
||||
return rv, nil
|
||||
default:
|
||||
}
|
||||
|
||||
t := time.NewTimer(ConnPoolAvailWaitTime)
|
||||
defer t.Stop()
|
||||
|
||||
// Try to grab an available connection within 1ms
|
||||
select {
|
||||
case rv, isopen := <-cp.connections:
|
||||
path = "avail1"
|
||||
if !isopen {
|
||||
return nil, errClosedPool
|
||||
}
|
||||
atomic.AddUint64(&cp.connCount, 1)
|
||||
return rv, nil
|
||||
case <-t.C:
|
||||
// No connection came around in time, let's see
|
||||
// whether we can get one or build a new one first.
|
||||
t.Reset(d) // Reuse the timer for the full timeout.
|
||||
select {
|
||||
case rv, isopen := <-cp.connections:
|
||||
path = "avail2"
|
||||
if !isopen {
|
||||
return nil, errClosedPool
|
||||
}
|
||||
atomic.AddUint64(&cp.connCount, 1)
|
||||
return rv, nil
|
||||
case cp.createsem <- true:
|
||||
path = "create"
|
||||
// Build a connection if we can't get a real one.
|
||||
// This can potentially be an overflow connection, or
|
||||
// a pooled connection.
|
||||
rv, err := cp.mkConn(cp.host, cp.auth)
|
||||
if err != nil {
|
||||
// On error, release our create hold
|
||||
<-cp.createsem
|
||||
} else {
|
||||
atomic.AddUint64(&cp.connCount, 1)
|
||||
}
|
||||
return rv, err
|
||||
case <-t.C:
|
||||
return nil, ErrTimeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *connectionPool) Get() (*memcached.Client, error) {
|
||||
return cp.GetWithTimeout(ConnPoolTimeout)
|
||||
}
|
||||
|
||||
func (cp *connectionPool) Return(c *memcached.Client) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if cp == nil {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
if c.IsHealthy() {
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
// This happens when the pool has already been
|
||||
// closed and we're trying to return a
|
||||
// connection to it anyway. Just close the
|
||||
// connection.
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case cp.connections <- c:
|
||||
default:
|
||||
<-cp.createsem
|
||||
c.Close()
|
||||
}
|
||||
} else {
|
||||
<-cp.createsem
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// give the ability to discard a connection from a pool
|
||||
// useful for ditching connections to the wrong node after a rebalance
|
||||
func (cp *connectionPool) Discard(c *memcached.Client) {
|
||||
<-cp.createsem
|
||||
c.Close()
|
||||
}
|
||||
|
||||
// asynchronous connection closer
|
||||
func (cp *connectionPool) connCloser() {
|
||||
var connCount uint64
|
||||
|
||||
t := time.NewTimer(ConnCloserInterval)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
connCount = cp.connCount
|
||||
|
||||
// we don't exist anymore! bail out!
|
||||
select {
|
||||
case <-cp.bailOut:
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
t.Reset(ConnCloserInterval)
|
||||
|
||||
// no overflow connections open or sustained requests for connections
|
||||
// nothing to do until the next cycle
|
||||
if len(cp.connections) <= cp.poolSize ||
|
||||
ConnCloserInterval/ConnPoolAvailWaitTime < time.Duration(cp.connCount-connCount) {
|
||||
continue
|
||||
}
|
||||
|
||||
// close overflow connections now that they are not needed
|
||||
for c := range cp.connections {
|
||||
select {
|
||||
case <-cp.bailOut:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// bail out if close did not work out
|
||||
if !cp.connCleanup(c) {
|
||||
return
|
||||
}
|
||||
if len(cp.connections) <= cp.poolSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// close connection with recovery on error
|
||||
func (cp *connectionPool) connCleanup(c *memcached.Client) (rv bool) {
|
||||
|
||||
// just in case we are closing a connection after
|
||||
// bailOut has been sent but we haven't yet read it
|
||||
defer func() {
|
||||
if recover() != nil {
|
||||
rv = false
|
||||
}
|
||||
}()
|
||||
rv = true
|
||||
|
||||
c.Close()
|
||||
<-cp.createsem
|
||||
return
|
||||
}
|
||||
|
||||
func (cp *connectionPool) StartTapFeed(args *memcached.TapArguments) (*memcached.TapFeed, error) {
|
||||
if cp == nil {
|
||||
return nil, errNoPool
|
||||
}
|
||||
mc, err := cp.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// A connection can't be used after TAP; Dont' count it against the
|
||||
// connection pool capacity
|
||||
<-cp.createsem
|
||||
|
||||
return mc.StartTapFeed(*args)
|
||||
}
|
||||
|
||||
const DEFAULT_WINDOW_SIZE = 20 * 1024 * 1024 // 20 Mb
|
||||
|
||||
func (cp *connectionPool) StartUprFeed(name string, sequence uint32, dcp_buffer_size uint32, data_chan_size int) (*memcached.UprFeed, error) {
|
||||
if cp == nil {
|
||||
return nil, errNoPool
|
||||
}
|
||||
mc, err := cp.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// A connection can't be used after it has been allocated to UPR;
|
||||
// Dont' count it against the connection pool capacity
|
||||
<-cp.createsem
|
||||
|
||||
uf, err := mc.NewUprFeed()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := uf.UprOpen(name, sequence, dcp_buffer_size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := uf.StartFeedWithConfig(data_chan_size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return uf, nil
|
||||
}
|
288
vendor/github.com/couchbaselabs/go-couchbase/ddocs.go
generated
vendored
Normal file
288
vendor/github.com/couchbaselabs/go-couchbase/ddocs.go
generated
vendored
Normal file
@ -0,0 +1,288 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/couchbase/goutils/logging"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ViewDefinition represents a single view within a design document.
|
||||
type ViewDefinition struct {
|
||||
Map string `json:"map"`
|
||||
Reduce string `json:"reduce,omitempty"`
|
||||
}
|
||||
|
||||
// DDoc is the document body of a design document specifying a view.
|
||||
type DDoc struct {
|
||||
Language string `json:"language,omitempty"`
|
||||
Views map[string]ViewDefinition `json:"views"`
|
||||
}
|
||||
|
||||
// DDocsResult represents the result from listing the design
|
||||
// documents.
|
||||
type DDocsResult struct {
|
||||
Rows []struct {
|
||||
DDoc struct {
|
||||
Meta map[string]interface{}
|
||||
JSON DDoc
|
||||
} `json:"doc"`
|
||||
} `json:"rows"`
|
||||
}
|
||||
|
||||
// GetDDocs lists all design documents
|
||||
func (b *Bucket) GetDDocs() (DDocsResult, error) {
|
||||
var ddocsResult DDocsResult
|
||||
b.RLock()
|
||||
pool := b.pool
|
||||
uri := b.DDocs.URI
|
||||
b.RUnlock()
|
||||
|
||||
// MB-23555 ephemeral buckets have no ddocs
|
||||
if uri == "" {
|
||||
return DDocsResult{}, nil
|
||||
}
|
||||
|
||||
err := pool.client.parseURLResponse(uri, &ddocsResult)
|
||||
if err != nil {
|
||||
return DDocsResult{}, err
|
||||
}
|
||||
return ddocsResult, nil
|
||||
}
|
||||
|
||||
func (b *Bucket) GetDDocWithRetry(docname string, into interface{}) error {
|
||||
ddocURI := fmt.Sprintf("/%s/_design/%s", b.GetName(), docname)
|
||||
err := b.parseAPIResponse(ddocURI, &into)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bucket) GetDDocsWithRetry() (DDocsResult, error) {
|
||||
var ddocsResult DDocsResult
|
||||
b.RLock()
|
||||
uri := b.DDocs.URI
|
||||
b.RUnlock()
|
||||
|
||||
// MB-23555 ephemeral buckets have no ddocs
|
||||
if uri == "" {
|
||||
return DDocsResult{}, nil
|
||||
}
|
||||
|
||||
err := b.parseURLResponse(uri, &ddocsResult)
|
||||
if err != nil {
|
||||
return DDocsResult{}, err
|
||||
}
|
||||
return ddocsResult, nil
|
||||
}
|
||||
|
||||
func (b *Bucket) ddocURL(docname string) (string, error) {
|
||||
u, err := b.randomBaseURL()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
u.Path = fmt.Sprintf("/%s/_design/%s", b.GetName(), docname)
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
func (b *Bucket) ddocURLNext(nodeId int, docname string) (string, int, error) {
|
||||
u, selected, err := b.randomNextURL(nodeId)
|
||||
if err != nil {
|
||||
return "", -1, err
|
||||
}
|
||||
u.Path = fmt.Sprintf("/%s/_design/%s", b.GetName(), docname)
|
||||
return u.String(), selected, nil
|
||||
}
|
||||
|
||||
const ABS_MAX_RETRIES = 10
|
||||
const ABS_MIN_RETRIES = 3
|
||||
|
||||
func (b *Bucket) getMaxRetries() (int, error) {
|
||||
|
||||
maxRetries := len(b.Nodes())
|
||||
|
||||
if maxRetries == 0 {
|
||||
return 0, fmt.Errorf("No available Couch rest URLs")
|
||||
}
|
||||
|
||||
if maxRetries > ABS_MAX_RETRIES {
|
||||
maxRetries = ABS_MAX_RETRIES
|
||||
} else if maxRetries < ABS_MIN_RETRIES {
|
||||
maxRetries = ABS_MIN_RETRIES
|
||||
}
|
||||
|
||||
return maxRetries, nil
|
||||
}
|
||||
|
||||
// PutDDoc installs a design document.
|
||||
func (b *Bucket) PutDDoc(docname string, value interface{}) error {
|
||||
|
||||
var Err error
|
||||
|
||||
maxRetries, err := b.getMaxRetries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastNode := START_NODE_ID
|
||||
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
|
||||
Err = nil
|
||||
|
||||
ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastNode = selectedNode
|
||||
|
||||
logging.Infof(" Trying with selected node %d", selectedNode)
|
||||
j, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("PUT", ddocU, bytes.NewReader(j))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
err = maybeAddAuth(req, b.authHandler(false /* bucket not yet locked */))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := doHTTPRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.StatusCode != 201 {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
Err = fmt.Errorf("error installing view: %v / %s",
|
||||
res.Status, body)
|
||||
logging.Errorf(" Error in PutDDOC %v. Retrying...", Err)
|
||||
res.Body.Close()
|
||||
b.Refresh()
|
||||
continue
|
||||
}
|
||||
|
||||
res.Body.Close()
|
||||
break
|
||||
}
|
||||
|
||||
return Err
|
||||
}
|
||||
|
||||
// GetDDoc retrieves a specific a design doc.
|
||||
func (b *Bucket) GetDDoc(docname string, into interface{}) error {
|
||||
var Err error
|
||||
var res *http.Response
|
||||
|
||||
maxRetries, err := b.getMaxRetries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastNode := START_NODE_ID
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
|
||||
Err = nil
|
||||
ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastNode = selectedNode
|
||||
logging.Infof(" Trying with selected node %d", selectedNode)
|
||||
|
||||
req, err := http.NewRequest("GET", ddocU, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
err = maybeAddAuth(req, b.authHandler(false /* bucket not yet locked */))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err = doHTTPRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
Err = fmt.Errorf("error reading view: %v / %s",
|
||||
res.Status, body)
|
||||
logging.Errorf(" Error in GetDDOC %v Retrying...", Err)
|
||||
b.Refresh()
|
||||
res.Body.Close()
|
||||
continue
|
||||
}
|
||||
defer res.Body.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if Err != nil {
|
||||
return Err
|
||||
}
|
||||
|
||||
d := json.NewDecoder(res.Body)
|
||||
return d.Decode(into)
|
||||
}
|
||||
|
||||
// DeleteDDoc removes a design document.
|
||||
func (b *Bucket) DeleteDDoc(docname string) error {
|
||||
|
||||
var Err error
|
||||
|
||||
maxRetries, err := b.getMaxRetries()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastNode := START_NODE_ID
|
||||
|
||||
for retryCount := 0; retryCount < maxRetries; retryCount++ {
|
||||
|
||||
Err = nil
|
||||
ddocU, selectedNode, err := b.ddocURLNext(lastNode, docname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lastNode = selectedNode
|
||||
logging.Infof(" Trying with selected node %d", selectedNode)
|
||||
|
||||
req, err := http.NewRequest("DELETE", ddocU, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
err = maybeAddAuth(req, b.authHandler(false /* bucket not already locked */))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := doHTTPRequest(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
Err = fmt.Errorf("error deleting view : %v / %s", res.Status, body)
|
||||
logging.Errorf(" Error in DeleteDDOC %v. Retrying ... ", Err)
|
||||
b.Refresh()
|
||||
res.Body.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
res.Body.Close()
|
||||
break
|
||||
}
|
||||
return Err
|
||||
}
|
300
vendor/github.com/couchbaselabs/go-couchbase/observe.go
generated
vendored
Normal file
300
vendor/github.com/couchbaselabs/go-couchbase/observe.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/couchbase/goutils/logging"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PersistTo uint8
|
||||
|
||||
const (
|
||||
PersistNone = PersistTo(0x00)
|
||||
PersistMaster = PersistTo(0x01)
|
||||
PersistOne = PersistTo(0x02)
|
||||
PersistTwo = PersistTo(0x03)
|
||||
PersistThree = PersistTo(0x04)
|
||||
PersistFour = PersistTo(0x05)
|
||||
)
|
||||
|
||||
type ObserveTo uint8
|
||||
|
||||
const (
|
||||
ObserveNone = ObserveTo(0x00)
|
||||
ObserveReplicateOne = ObserveTo(0x01)
|
||||
ObserveReplicateTwo = ObserveTo(0x02)
|
||||
ObserveReplicateThree = ObserveTo(0x03)
|
||||
ObserveReplicateFour = ObserveTo(0x04)
|
||||
)
|
||||
|
||||
type JobType uint8
|
||||
|
||||
const (
|
||||
OBSERVE = JobType(0x00)
|
||||
PERSIST = JobType(0x01)
|
||||
)
|
||||
|
||||
type ObservePersistJob struct {
|
||||
vb uint16
|
||||
vbuuid uint64
|
||||
hostname string
|
||||
jobType JobType
|
||||
failover uint8
|
||||
lastPersistedSeqNo uint64
|
||||
currentSeqNo uint64
|
||||
resultChan chan *ObservePersistJob
|
||||
errorChan chan *OPErrResponse
|
||||
}
|
||||
|
||||
type OPErrResponse struct {
|
||||
vb uint16
|
||||
vbuuid uint64
|
||||
err error
|
||||
job *ObservePersistJob
|
||||
}
|
||||
|
||||
var ObservePersistPool = NewPool(1024)
|
||||
var OPJobChan = make(chan *ObservePersistJob, 1024)
|
||||
var OPJobDone = make(chan bool)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
func (b *Bucket) StartOPPollers(maxWorkers int) {
|
||||
|
||||
for i := 0; i < maxWorkers; i++ {
|
||||
go b.OPJobPoll()
|
||||
wg.Add(1)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (b *Bucket) SetObserveAndPersist(nPersist PersistTo, nObserve ObserveTo) (err error) {
|
||||
|
||||
numNodes := len(b.Nodes())
|
||||
if int(nPersist) > numNodes || int(nObserve) > numNodes {
|
||||
return fmt.Errorf("Not enough healthy nodes in the cluster")
|
||||
}
|
||||
|
||||
if int(nPersist) > (b.Replicas+1) || int(nObserve) > b.Replicas {
|
||||
return fmt.Errorf("Not enough replicas in the cluster")
|
||||
}
|
||||
|
||||
if EnableMutationToken == false {
|
||||
return fmt.Errorf("Mutation Tokens not enabled ")
|
||||
}
|
||||
|
||||
b.ds = &DurablitySettings{Persist: PersistTo(nPersist), Observe: ObserveTo(nObserve)}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Bucket) ObserveAndPersistPoll(vb uint16, vbuuid uint64, seqNo uint64) (err error, failover bool) {
|
||||
b.RLock()
|
||||
ds := b.ds
|
||||
b.RUnlock()
|
||||
|
||||
if ds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
nj := 0 // total number of jobs
|
||||
resultChan := make(chan *ObservePersistJob, 10)
|
||||
errChan := make(chan *OPErrResponse, 10)
|
||||
|
||||
nodes := b.GetNodeList(vb)
|
||||
if int(ds.Observe) > len(nodes) || int(ds.Persist) > len(nodes) {
|
||||
return fmt.Errorf("Not enough healthy nodes in the cluster"), false
|
||||
}
|
||||
|
||||
logging.Infof("Node list %v", nodes)
|
||||
|
||||
if ds.Observe >= ObserveReplicateOne {
|
||||
// create a job for each host
|
||||
for i := ObserveReplicateOne; i < ds.Observe+1; i++ {
|
||||
opJob := ObservePersistPool.Get()
|
||||
opJob.vb = vb
|
||||
opJob.vbuuid = vbuuid
|
||||
opJob.jobType = OBSERVE
|
||||
opJob.hostname = nodes[i]
|
||||
opJob.resultChan = resultChan
|
||||
opJob.errorChan = errChan
|
||||
|
||||
OPJobChan <- opJob
|
||||
nj++
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ds.Persist >= PersistMaster {
|
||||
for i := PersistMaster; i < ds.Persist+1; i++ {
|
||||
opJob := ObservePersistPool.Get()
|
||||
opJob.vb = vb
|
||||
opJob.vbuuid = vbuuid
|
||||
opJob.jobType = PERSIST
|
||||
opJob.hostname = nodes[i]
|
||||
opJob.resultChan = resultChan
|
||||
opJob.errorChan = errChan
|
||||
|
||||
OPJobChan <- opJob
|
||||
nj++
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ok := true
|
||||
for ok {
|
||||
select {
|
||||
case res := <-resultChan:
|
||||
jobDone := false
|
||||
if res.failover == 0 {
|
||||
// no failover
|
||||
if res.jobType == PERSIST {
|
||||
if res.lastPersistedSeqNo >= seqNo {
|
||||
jobDone = true
|
||||
}
|
||||
|
||||
} else {
|
||||
if res.currentSeqNo >= seqNo {
|
||||
jobDone = true
|
||||
}
|
||||
}
|
||||
|
||||
if jobDone == true {
|
||||
nj--
|
||||
ObservePersistPool.Put(res)
|
||||
} else {
|
||||
// requeue this job
|
||||
OPJobChan <- res
|
||||
}
|
||||
|
||||
} else {
|
||||
// Not currently handling failover scenarios TODO
|
||||
nj--
|
||||
ObservePersistPool.Put(res)
|
||||
failover = true
|
||||
}
|
||||
|
||||
if nj == 0 {
|
||||
// done with all the jobs
|
||||
ok = false
|
||||
close(resultChan)
|
||||
close(errChan)
|
||||
}
|
||||
|
||||
case Err := <-errChan:
|
||||
logging.Errorf("Error in Observe/Persist %v", Err.err)
|
||||
err = fmt.Errorf("Error in Observe/Persist job %v", Err.err)
|
||||
nj--
|
||||
ObservePersistPool.Put(Err.job)
|
||||
if nj == 0 {
|
||||
close(resultChan)
|
||||
close(errChan)
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Bucket) OPJobPoll() {
|
||||
|
||||
ok := true
|
||||
for ok == true {
|
||||
select {
|
||||
case job := <-OPJobChan:
|
||||
pool := b.getConnPoolByHost(job.hostname, false /* bucket not already locked */)
|
||||
if pool == nil {
|
||||
errRes := &OPErrResponse{vb: job.vb, vbuuid: job.vbuuid}
|
||||
errRes.err = fmt.Errorf("Pool not found for host %v", job.hostname)
|
||||
errRes.job = job
|
||||
job.errorChan <- errRes
|
||||
continue
|
||||
}
|
||||
conn, err := pool.Get()
|
||||
if err != nil {
|
||||
errRes := &OPErrResponse{vb: job.vb, vbuuid: job.vbuuid}
|
||||
errRes.err = fmt.Errorf("Unable to get connection from pool %v", err)
|
||||
errRes.job = job
|
||||
job.errorChan <- errRes
|
||||
continue
|
||||
}
|
||||
|
||||
res, err := conn.ObserveSeq(job.vb, job.vbuuid)
|
||||
if err != nil {
|
||||
errRes := &OPErrResponse{vb: job.vb, vbuuid: job.vbuuid}
|
||||
errRes.err = fmt.Errorf("Command failed %v", err)
|
||||
errRes.job = job
|
||||
job.errorChan <- errRes
|
||||
continue
|
||||
|
||||
}
|
||||
pool.Return(conn)
|
||||
job.lastPersistedSeqNo = res.LastPersistedSeqNo
|
||||
job.currentSeqNo = res.CurrentSeqNo
|
||||
job.failover = res.Failover
|
||||
|
||||
job.resultChan <- job
|
||||
case <-OPJobDone:
|
||||
logging.Infof("Observe Persist Poller exitting")
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func (b *Bucket) GetNodeList(vb uint16) []string {
|
||||
|
||||
vbm := b.VBServerMap()
|
||||
if len(vbm.VBucketMap) < int(vb) {
|
||||
logging.Infof("vbmap smaller than vblist")
|
||||
return nil
|
||||
}
|
||||
|
||||
nodes := make([]string, len(vbm.VBucketMap[vb]))
|
||||
for i := 0; i < len(vbm.VBucketMap[vb]); i++ {
|
||||
n := vbm.VBucketMap[vb][i]
|
||||
if n < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
node := b.getMasterNode(n)
|
||||
if len(node) > 1 {
|
||||
nodes[i] = node
|
||||
}
|
||||
continue
|
||||
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
//pool of ObservePersist Jobs
|
||||
type OPpool struct {
|
||||
pool chan *ObservePersistJob
|
||||
}
|
||||
|
||||
// NewPool creates a new pool of jobs
|
||||
func NewPool(max int) *OPpool {
|
||||
return &OPpool{
|
||||
pool: make(chan *ObservePersistJob, max),
|
||||
}
|
||||
}
|
||||
|
||||
// Borrow a Client from the pool.
|
||||
func (p *OPpool) Get() *ObservePersistJob {
|
||||
var o *ObservePersistJob
|
||||
select {
|
||||
case o = <-p.pool:
|
||||
default:
|
||||
o = &ObservePersistJob{}
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
// Return returns a Client to the pool.
|
||||
func (p *OPpool) Put(o *ObservePersistJob) {
|
||||
select {
|
||||
case p.pool <- o:
|
||||
default:
|
||||
// let it go, let it go...
|
||||
}
|
||||
}
|
1282
vendor/github.com/couchbaselabs/go-couchbase/pools.go
generated
vendored
Normal file
1282
vendor/github.com/couchbaselabs/go-couchbase/pools.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
209
vendor/github.com/couchbaselabs/go-couchbase/streaming.go
generated
vendored
Normal file
209
vendor/github.com/couchbaselabs/go-couchbase/streaming.go
generated
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/couchbase/goutils/logging"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Bucket auto-updater gets the latest version of the bucket config from
|
||||
// the server. If the configuration has changed then updated the local
|
||||
// bucket information. If the bucket has been deleted then notify anyone
|
||||
// who is holding a reference to this bucket
|
||||
|
||||
const MAX_RETRY_COUNT = 5
|
||||
const DISCONNECT_PERIOD = 120 * time.Second
|
||||
|
||||
type NotifyFn func(bucket string, err error)
|
||||
|
||||
// Use TCP keepalive to detect half close sockets
|
||||
var updaterTransport http.RoundTripper = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
}
|
||||
|
||||
var updaterHTTPClient = &http.Client{Transport: updaterTransport}
|
||||
|
||||
func doHTTPRequestForUpdate(req *http.Request) (*http.Response, error) {
|
||||
|
||||
var err error
|
||||
var res *http.Response
|
||||
|
||||
for i := 0; i < HTTP_MAX_RETRY; i++ {
|
||||
res, err = updaterHTTPClient.Do(req)
|
||||
if err != nil && isHttpConnError(err) {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (b *Bucket) RunBucketUpdater(notify NotifyFn) {
|
||||
go func() {
|
||||
err := b.UpdateBucket()
|
||||
if err != nil {
|
||||
if notify != nil {
|
||||
notify(b.GetName(), err)
|
||||
}
|
||||
logging.Errorf(" Bucket Updater exited with err %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (b *Bucket) replaceConnPools2(with []*connectionPool, bucketLocked bool) {
|
||||
if !bucketLocked {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
}
|
||||
old := b.connPools
|
||||
b.connPools = unsafe.Pointer(&with)
|
||||
if old != nil {
|
||||
for _, pool := range *(*[]*connectionPool)(old) {
|
||||
if pool != nil && pool.inUse == false {
|
||||
pool.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *Bucket) UpdateBucket() error {
|
||||
|
||||
var failures int
|
||||
var returnErr error
|
||||
|
||||
for {
|
||||
|
||||
if failures == MAX_RETRY_COUNT {
|
||||
logging.Errorf(" Maximum failures reached. Exiting loop...")
|
||||
return fmt.Errorf("Max failures reached. Last Error %v", returnErr)
|
||||
}
|
||||
|
||||
nodes := b.Nodes()
|
||||
if len(nodes) < 1 {
|
||||
return fmt.Errorf("No healthy nodes found")
|
||||
}
|
||||
|
||||
startNode := rand.Intn(len(nodes))
|
||||
node := nodes[(startNode)%len(nodes)]
|
||||
|
||||
streamUrl := fmt.Sprintf("http://%s/pools/default/bucketsStreaming/%s", node.Hostname, b.GetName())
|
||||
logging.Infof(" Trying with %s", streamUrl)
|
||||
req, err := http.NewRequest("GET", streamUrl, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.RLock()
|
||||
pool := b.pool
|
||||
bucketName := b.Name
|
||||
b.RUnlock()
|
||||
scopes, err := getScopesAndCollections(pool, bucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lock here to avoid having pool closed under us.
|
||||
b.RLock()
|
||||
err = maybeAddAuth(req, b.pool.client.ah)
|
||||
b.RUnlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := doHTTPRequestForUpdate(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
bod, _ := ioutil.ReadAll(io.LimitReader(res.Body, 512))
|
||||
logging.Errorf("Failed to connect to host, unexpected status code: %v. Body %s", res.StatusCode, bod)
|
||||
res.Body.Close()
|
||||
returnErr = fmt.Errorf("Failed to connect to host. Status %v Body %s", res.StatusCode, bod)
|
||||
failures++
|
||||
continue
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(res.Body)
|
||||
|
||||
tmpb := &Bucket{}
|
||||
for {
|
||||
|
||||
err := dec.Decode(&tmpb)
|
||||
if err != nil {
|
||||
returnErr = err
|
||||
res.Body.Close()
|
||||
break
|
||||
}
|
||||
|
||||
// if we got here, reset failure count
|
||||
failures = 0
|
||||
b.Lock()
|
||||
|
||||
// mark all the old connection pools for deletion
|
||||
pools := b.getConnPools(true /* already locked */)
|
||||
for _, pool := range pools {
|
||||
if pool != nil {
|
||||
pool.inUse = false
|
||||
}
|
||||
}
|
||||
|
||||
newcps := make([]*connectionPool, len(tmpb.VBSMJson.ServerList))
|
||||
for i := range newcps {
|
||||
// get the old connection pool and check if it is still valid
|
||||
pool := b.getConnPoolByHost(tmpb.VBSMJson.ServerList[i], true /* bucket already locked */)
|
||||
if pool != nil && pool.inUse == false {
|
||||
// if the hostname and index is unchanged then reuse this pool
|
||||
newcps[i] = pool
|
||||
pool.inUse = true
|
||||
continue
|
||||
}
|
||||
// else create a new pool
|
||||
if b.ah != nil {
|
||||
newcps[i] = newConnectionPool(
|
||||
tmpb.VBSMJson.ServerList[i],
|
||||
b.ah, false, PoolSize, PoolOverflow)
|
||||
|
||||
} else {
|
||||
newcps[i] = newConnectionPool(
|
||||
tmpb.VBSMJson.ServerList[i],
|
||||
b.authHandler(true /* bucket already locked */),
|
||||
false, PoolSize, PoolOverflow)
|
||||
}
|
||||
}
|
||||
|
||||
b.replaceConnPools2(newcps, true /* bucket already locked */)
|
||||
|
||||
tmpb.ah = b.ah
|
||||
b.vBucketServerMap = unsafe.Pointer(&tmpb.VBSMJson)
|
||||
b.nodeList = unsafe.Pointer(&tmpb.NodesJSON)
|
||||
b.Scopes = scopes
|
||||
b.Unlock()
|
||||
|
||||
logging.Infof("Got new configuration for bucket %s", b.GetName())
|
||||
|
||||
}
|
||||
// we are here because of an error
|
||||
failures++
|
||||
continue
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
143
vendor/github.com/couchbaselabs/go-couchbase/tap.go
generated
vendored
Normal file
143
vendor/github.com/couchbaselabs/go-couchbase/tap.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"github.com/couchbase/gomemcached/client"
|
||||
"github.com/couchbase/goutils/logging"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const initialRetryInterval = 1 * time.Second
|
||||
const maximumRetryInterval = 30 * time.Second
|
||||
|
||||
// A TapFeed streams mutation events from a bucket.
|
||||
//
|
||||
// Events from the bucket can be read from the channel 'C'. Remember
|
||||
// to call Close() on it when you're done, unless its channel has
|
||||
// closed itself already.
|
||||
type TapFeed struct {
|
||||
C <-chan memcached.TapEvent
|
||||
|
||||
bucket *Bucket
|
||||
args *memcached.TapArguments
|
||||
nodeFeeds []*memcached.TapFeed // The TAP feeds of the individual nodes
|
||||
output chan memcached.TapEvent // Same as C but writeably-typed
|
||||
wg sync.WaitGroup
|
||||
quit chan bool
|
||||
}
|
||||
|
||||
// StartTapFeed creates and starts a new Tap feed
|
||||
func (b *Bucket) StartTapFeed(args *memcached.TapArguments) (*TapFeed, error) {
|
||||
if args == nil {
|
||||
defaultArgs := memcached.DefaultTapArguments()
|
||||
args = &defaultArgs
|
||||
}
|
||||
|
||||
feed := &TapFeed{
|
||||
bucket: b,
|
||||
args: args,
|
||||
output: make(chan memcached.TapEvent, 10),
|
||||
quit: make(chan bool),
|
||||
}
|
||||
|
||||
go feed.run()
|
||||
|
||||
feed.C = feed.output
|
||||
return feed, nil
|
||||
}
|
||||
|
||||
// Goroutine that runs the feed
|
||||
func (feed *TapFeed) run() {
|
||||
retryInterval := initialRetryInterval
|
||||
bucketOK := true
|
||||
for {
|
||||
// Connect to the TAP feed of each server node:
|
||||
if bucketOK {
|
||||
killSwitch, err := feed.connectToNodes()
|
||||
if err == nil {
|
||||
// Run until one of the sub-feeds fails:
|
||||
select {
|
||||
case <-killSwitch:
|
||||
case <-feed.quit:
|
||||
return
|
||||
}
|
||||
feed.closeNodeFeeds()
|
||||
retryInterval = initialRetryInterval
|
||||
}
|
||||
}
|
||||
|
||||
// On error, try to refresh the bucket in case the list of nodes changed:
|
||||
logging.Infof("go-couchbase: TAP connection lost; reconnecting to bucket %q in %v",
|
||||
feed.bucket.Name, retryInterval)
|
||||
err := feed.bucket.Refresh()
|
||||
bucketOK = err == nil
|
||||
|
||||
select {
|
||||
case <-time.After(retryInterval):
|
||||
case <-feed.quit:
|
||||
return
|
||||
}
|
||||
if retryInterval *= 2; retryInterval > maximumRetryInterval {
|
||||
retryInterval = maximumRetryInterval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (feed *TapFeed) connectToNodes() (killSwitch chan bool, err error) {
|
||||
killSwitch = make(chan bool)
|
||||
for _, serverConn := range feed.bucket.getConnPools(false /* not already locked */) {
|
||||
var singleFeed *memcached.TapFeed
|
||||
singleFeed, err = serverConn.StartTapFeed(feed.args)
|
||||
if err != nil {
|
||||
logging.Errorf("go-couchbase: Error connecting to tap feed of %s: %v", serverConn.host, err)
|
||||
feed.closeNodeFeeds()
|
||||
return
|
||||
}
|
||||
feed.nodeFeeds = append(feed.nodeFeeds, singleFeed)
|
||||
go feed.forwardTapEvents(singleFeed, killSwitch, serverConn.host)
|
||||
feed.wg.Add(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Goroutine that forwards Tap events from a single node's feed to the aggregate feed.
|
||||
func (feed *TapFeed) forwardTapEvents(singleFeed *memcached.TapFeed, killSwitch chan bool, host string) {
|
||||
defer feed.wg.Done()
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-singleFeed.C:
|
||||
if !ok {
|
||||
if singleFeed.Error != nil {
|
||||
logging.Errorf("go-couchbase: Tap feed from %s failed: %v", host, singleFeed.Error)
|
||||
}
|
||||
killSwitch <- true
|
||||
return
|
||||
}
|
||||
feed.output <- event
|
||||
case <-feed.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (feed *TapFeed) closeNodeFeeds() {
|
||||
for _, f := range feed.nodeFeeds {
|
||||
f.Close()
|
||||
}
|
||||
feed.nodeFeeds = nil
|
||||
}
|
||||
|
||||
// Close a Tap feed.
|
||||
func (feed *TapFeed) Close() error {
|
||||
select {
|
||||
case <-feed.quit:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
feed.closeNodeFeeds()
|
||||
close(feed.quit)
|
||||
feed.wg.Wait()
|
||||
close(feed.output)
|
||||
return nil
|
||||
}
|
398
vendor/github.com/couchbaselabs/go-couchbase/upr.go
generated
vendored
Normal file
398
vendor/github.com/couchbaselabs/go-couchbase/upr.go
generated
vendored
Normal file
@ -0,0 +1,398 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
"github.com/couchbase/gomemcached"
|
||||
"github.com/couchbase/gomemcached/client"
|
||||
"github.com/couchbase/goutils/logging"
|
||||
)
|
||||
|
||||
// A UprFeed streams mutation events from a bucket.
|
||||
//
|
||||
// Events from the bucket can be read from the channel 'C'. Remember
|
||||
// to call Close() on it when you're done, unless its channel has
|
||||
// closed itself already.
|
||||
type UprFeed struct {
|
||||
C <-chan *memcached.UprEvent
|
||||
|
||||
bucket *Bucket
|
||||
nodeFeeds map[string]*FeedInfo // The UPR feeds of the individual nodes
|
||||
output chan *memcached.UprEvent // Same as C but writeably-typed
|
||||
outputClosed bool
|
||||
quit chan bool
|
||||
name string // name of this UPR feed
|
||||
sequence uint32 // sequence number for this feed
|
||||
connected bool
|
||||
killSwitch chan bool
|
||||
closing bool
|
||||
wg sync.WaitGroup
|
||||
dcp_buffer_size uint32
|
||||
data_chan_size int
|
||||
}
|
||||
|
||||
// UprFeed from a single connection
|
||||
type FeedInfo struct {
|
||||
uprFeed *memcached.UprFeed // UPR feed handle
|
||||
host string // hostname
|
||||
connected bool // connected
|
||||
quit chan bool // quit channel
|
||||
}
|
||||
|
||||
type FailoverLog map[uint16]memcached.FailoverLog
|
||||
|
||||
// GetFailoverLogs, get the failover logs for a set of vbucket ids
|
||||
func (b *Bucket) GetFailoverLogs(vBuckets []uint16) (FailoverLog, error) {
|
||||
|
||||
// map vbids to their corresponding hosts
|
||||
vbHostList := make(map[string][]uint16)
|
||||
vbm := b.VBServerMap()
|
||||
if len(vbm.VBucketMap) < len(vBuckets) {
|
||||
return nil, fmt.Errorf("vbmap smaller than vbucket list: %v vs. %v",
|
||||
vbm.VBucketMap, vBuckets)
|
||||
}
|
||||
|
||||
for _, vb := range vBuckets {
|
||||
masterID := vbm.VBucketMap[vb][0]
|
||||
master := b.getMasterNode(masterID)
|
||||
if master == "" {
|
||||
return nil, fmt.Errorf("No master found for vb %d", vb)
|
||||
}
|
||||
|
||||
vbList := vbHostList[master]
|
||||
if vbList == nil {
|
||||
vbList = make([]uint16, 0)
|
||||
}
|
||||
vbList = append(vbList, vb)
|
||||
vbHostList[master] = vbList
|
||||
}
|
||||
|
||||
failoverLogMap := make(FailoverLog)
|
||||
for _, serverConn := range b.getConnPools(false /* not already locked */) {
|
||||
|
||||
vbList := vbHostList[serverConn.host]
|
||||
if vbList == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
mc, err := serverConn.Get()
|
||||
if err != nil {
|
||||
logging.Infof("No Free connections for vblist %v", vbList)
|
||||
return nil, fmt.Errorf("No Free connections for host %s",
|
||||
serverConn.host)
|
||||
|
||||
}
|
||||
// close the connection so that it doesn't get reused for upr data
|
||||
// connection
|
||||
defer mc.Close()
|
||||
failoverlogs, err := mc.UprGetFailoverLog(vbList)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting failover log %s host %s",
|
||||
err.Error(), serverConn.host)
|
||||
|
||||
}
|
||||
|
||||
for vb, log := range failoverlogs {
|
||||
failoverLogMap[vb] = *log
|
||||
}
|
||||
}
|
||||
|
||||
return failoverLogMap, nil
|
||||
}
|
||||
|
||||
func (b *Bucket) StartUprFeed(name string, sequence uint32) (*UprFeed, error) {
|
||||
return b.StartUprFeedWithConfig(name, sequence, 10, DEFAULT_WINDOW_SIZE)
|
||||
}
|
||||
|
||||
// StartUprFeed creates and starts a new Upr feed
|
||||
// No data will be sent on the channel unless vbuckets streams are requested
|
||||
func (b *Bucket) StartUprFeedWithConfig(name string, sequence uint32, data_chan_size int, dcp_buffer_size uint32) (*UprFeed, error) {
|
||||
|
||||
feed := &UprFeed{
|
||||
bucket: b,
|
||||
output: make(chan *memcached.UprEvent, data_chan_size),
|
||||
quit: make(chan bool),
|
||||
nodeFeeds: make(map[string]*FeedInfo, 0),
|
||||
name: name,
|
||||
sequence: sequence,
|
||||
killSwitch: make(chan bool),
|
||||
dcp_buffer_size: dcp_buffer_size,
|
||||
data_chan_size: data_chan_size,
|
||||
}
|
||||
|
||||
err := feed.connectToNodes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot connect to bucket %s", err.Error())
|
||||
}
|
||||
feed.connected = true
|
||||
go feed.run()
|
||||
|
||||
feed.C = feed.output
|
||||
return feed, nil
|
||||
}
|
||||
|
||||
// UprRequestStream starts a stream for a vb on a feed
|
||||
func (feed *UprFeed) UprRequestStream(vb uint16, opaque uint16, flags uint32,
|
||||
vuuid, startSequence, endSequence, snapStart, snapEnd uint64) error {
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Panicf("Panic in UprRequestStream. Feed %v Bucket %v", feed, feed.bucket)
|
||||
}
|
||||
}()
|
||||
|
||||
vbm := feed.bucket.VBServerMap()
|
||||
if len(vbm.VBucketMap) < int(vb) {
|
||||
return fmt.Errorf("vbmap smaller than vbucket list: %v vs. %v",
|
||||
vb, vbm.VBucketMap)
|
||||
}
|
||||
|
||||
if int(vb) >= len(vbm.VBucketMap) {
|
||||
return fmt.Errorf("Invalid vbucket id %d", vb)
|
||||
}
|
||||
|
||||
masterID := vbm.VBucketMap[vb][0]
|
||||
master := feed.bucket.getMasterNode(masterID)
|
||||
if master == "" {
|
||||
return fmt.Errorf("Master node not found for vbucket %d", vb)
|
||||
}
|
||||
singleFeed := feed.nodeFeeds[master]
|
||||
if singleFeed == nil {
|
||||
return fmt.Errorf("UprFeed for this host not found")
|
||||
}
|
||||
|
||||
if err := singleFeed.uprFeed.UprRequestStream(vb, opaque, flags,
|
||||
vuuid, startSequence, endSequence, snapStart, snapEnd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UprCloseStream ends a vbucket stream.
|
||||
func (feed *UprFeed) UprCloseStream(vb, opaqueMSB uint16) error {
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Panicf("Panic in UprCloseStream. Feed %v Bucket %v ", feed, feed.bucket)
|
||||
}
|
||||
}()
|
||||
|
||||
vbm := feed.bucket.VBServerMap()
|
||||
if len(vbm.VBucketMap) < int(vb) {
|
||||
return fmt.Errorf("vbmap smaller than vbucket list: %v vs. %v",
|
||||
vb, vbm.VBucketMap)
|
||||
}
|
||||
|
||||
if int(vb) >= len(vbm.VBucketMap) {
|
||||
return fmt.Errorf("Invalid vbucket id %d", vb)
|
||||
}
|
||||
|
||||
masterID := vbm.VBucketMap[vb][0]
|
||||
master := feed.bucket.getMasterNode(masterID)
|
||||
if master == "" {
|
||||
return fmt.Errorf("Master node not found for vbucket %d", vb)
|
||||
}
|
||||
singleFeed := feed.nodeFeeds[master]
|
||||
if singleFeed == nil {
|
||||
return fmt.Errorf("UprFeed for this host not found")
|
||||
}
|
||||
|
||||
if err := singleFeed.uprFeed.CloseStream(vb, opaqueMSB); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Goroutine that runs the feed
|
||||
func (feed *UprFeed) run() {
|
||||
retryInterval := initialRetryInterval
|
||||
bucketOK := true
|
||||
for {
|
||||
// Connect to the UPR feed of each server node:
|
||||
if bucketOK {
|
||||
// Run until one of the sub-feeds fails:
|
||||
select {
|
||||
case <-feed.killSwitch:
|
||||
case <-feed.quit:
|
||||
return
|
||||
}
|
||||
//feed.closeNodeFeeds()
|
||||
retryInterval = initialRetryInterval
|
||||
}
|
||||
|
||||
if feed.closing == true {
|
||||
// we have been asked to shut down
|
||||
return
|
||||
}
|
||||
|
||||
// On error, try to refresh the bucket in case the list of nodes changed:
|
||||
logging.Infof("go-couchbase: UPR connection lost; reconnecting to bucket %q in %v",
|
||||
feed.bucket.Name, retryInterval)
|
||||
|
||||
if err := feed.bucket.Refresh(); err != nil {
|
||||
// if we fail to refresh the bucket, exit the feed
|
||||
// MB-14917
|
||||
logging.Infof("Unable to refresh bucket %s ", err.Error())
|
||||
close(feed.output)
|
||||
feed.outputClosed = true
|
||||
feed.closeNodeFeeds()
|
||||
return
|
||||
}
|
||||
|
||||
// this will only connect to nodes that are not connected or changed
|
||||
// user will have to reconnect the stream
|
||||
err := feed.connectToNodes()
|
||||
if err != nil {
|
||||
logging.Infof("Unable to connect to nodes..exit ")
|
||||
close(feed.output)
|
||||
feed.outputClosed = true
|
||||
feed.closeNodeFeeds()
|
||||
return
|
||||
}
|
||||
bucketOK = err == nil
|
||||
|
||||
select {
|
||||
case <-time.After(retryInterval):
|
||||
case <-feed.quit:
|
||||
return
|
||||
}
|
||||
if retryInterval *= 2; retryInterval > maximumRetryInterval {
|
||||
retryInterval = maximumRetryInterval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (feed *UprFeed) connectToNodes() (err error) {
|
||||
nodeCount := 0
|
||||
for _, serverConn := range feed.bucket.getConnPools(false /* not already locked */) {
|
||||
|
||||
// this maybe a reconnection, so check if the connection to the node
|
||||
// already exists. Connect only if the node is not found in the list
|
||||
// or connected == false
|
||||
nodeFeed := feed.nodeFeeds[serverConn.host]
|
||||
|
||||
if nodeFeed != nil && nodeFeed.connected == true {
|
||||
continue
|
||||
}
|
||||
|
||||
var singleFeed *memcached.UprFeed
|
||||
var name string
|
||||
if feed.name == "" {
|
||||
name = "DefaultUprClient"
|
||||
} else {
|
||||
name = feed.name
|
||||
}
|
||||
singleFeed, err = serverConn.StartUprFeed(name, feed.sequence, feed.dcp_buffer_size, feed.data_chan_size)
|
||||
if err != nil {
|
||||
logging.Errorf("go-couchbase: Error connecting to upr feed of %s: %v", serverConn.host, err)
|
||||
feed.closeNodeFeeds()
|
||||
return
|
||||
}
|
||||
// add the node to the connection map
|
||||
feedInfo := &FeedInfo{
|
||||
uprFeed: singleFeed,
|
||||
connected: true,
|
||||
host: serverConn.host,
|
||||
quit: make(chan bool),
|
||||
}
|
||||
feed.nodeFeeds[serverConn.host] = feedInfo
|
||||
go feed.forwardUprEvents(feedInfo, feed.killSwitch, serverConn.host)
|
||||
feed.wg.Add(1)
|
||||
nodeCount++
|
||||
}
|
||||
if nodeCount == 0 {
|
||||
return fmt.Errorf("No connection to bucket")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Goroutine that forwards Upr events from a single node's feed to the aggregate feed.
|
||||
func (feed *UprFeed) forwardUprEvents(nodeFeed *FeedInfo, killSwitch chan bool, host string) {
|
||||
singleFeed := nodeFeed.uprFeed
|
||||
|
||||
defer func() {
|
||||
feed.wg.Done()
|
||||
if r := recover(); r != nil {
|
||||
//if feed is not closing, re-throw the panic
|
||||
if feed.outputClosed != true && feed.closing != true {
|
||||
panic(r)
|
||||
} else {
|
||||
logging.Errorf("Panic is recovered. Since feed is closed, exit gracefully")
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-nodeFeed.quit:
|
||||
nodeFeed.connected = false
|
||||
return
|
||||
|
||||
case event, ok := <-singleFeed.C:
|
||||
if !ok {
|
||||
if singleFeed.Error != nil {
|
||||
logging.Errorf("go-couchbase: Upr feed from %s failed: %v", host, singleFeed.Error)
|
||||
}
|
||||
killSwitch <- true
|
||||
return
|
||||
}
|
||||
if feed.outputClosed == true {
|
||||
// someone closed the node feed
|
||||
logging.Infof("Node need closed, returning from forwardUprEvent")
|
||||
return
|
||||
}
|
||||
feed.output <- event
|
||||
if event.Status == gomemcached.NOT_MY_VBUCKET {
|
||||
logging.Infof(" Got a not my vbucket error !! ")
|
||||
if err := feed.bucket.Refresh(); err != nil {
|
||||
logging.Errorf("Unable to refresh bucket %s ", err.Error())
|
||||
feed.closeNodeFeeds()
|
||||
return
|
||||
}
|
||||
// this will only connect to nodes that are not connected or changed
|
||||
// user will have to reconnect the stream
|
||||
if err := feed.connectToNodes(); err != nil {
|
||||
logging.Errorf("Unable to connect to nodes %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (feed *UprFeed) closeNodeFeeds() {
|
||||
for _, f := range feed.nodeFeeds {
|
||||
logging.Infof(" Sending close to forwardUprEvent ")
|
||||
close(f.quit)
|
||||
f.uprFeed.Close()
|
||||
}
|
||||
feed.nodeFeeds = nil
|
||||
}
|
||||
|
||||
// Close a Upr feed.
|
||||
func (feed *UprFeed) Close() error {
|
||||
select {
|
||||
case <-feed.quit:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
feed.closing = true
|
||||
feed.closeNodeFeeds()
|
||||
close(feed.quit)
|
||||
|
||||
feed.wg.Wait()
|
||||
if feed.outputClosed == false {
|
||||
feed.outputClosed = true
|
||||
close(feed.output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
119
vendor/github.com/couchbaselabs/go-couchbase/users.go
generated
vendored
Normal file
119
vendor/github.com/couchbaselabs/go-couchbase/users.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Id string
|
||||
Domain string
|
||||
Roles []Role
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
Role string
|
||||
BucketName string `json:"bucket_name"`
|
||||
}
|
||||
|
||||
// Sample:
|
||||
// {"role":"admin","name":"Admin","desc":"Can manage ALL cluster features including security.","ce":true}
|
||||
// {"role":"query_select","bucket_name":"*","name":"Query Select","desc":"Can execute SELECT statement on bucket to retrieve data"}
|
||||
type RoleDescription struct {
|
||||
Role string
|
||||
Name string
|
||||
Desc string
|
||||
Ce bool
|
||||
BucketName string `json:"bucket_name"`
|
||||
}
|
||||
|
||||
// Return user-role data, as parsed JSON.
|
||||
// Sample:
|
||||
// [{"id":"ivanivanov","name":"Ivan Ivanov","roles":[{"role":"cluster_admin"},{"bucket_name":"default","role":"bucket_admin"}]},
|
||||
// {"id":"petrpetrov","name":"Petr Petrov","roles":[{"role":"replication_admin"}]}]
|
||||
func (c *Client) GetUserRoles() ([]interface{}, error) {
|
||||
ret := make([]interface{}, 0, 1)
|
||||
err := c.parseURLResponse("/settings/rbac/users", &ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the configured administrator.
|
||||
// Expected result: {"port":8091,"username":"Administrator"}
|
||||
adminInfo := make(map[string]interface{}, 2)
|
||||
err = c.parseURLResponse("/settings/web", &adminInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a special entry for the configured administrator.
|
||||
adminResult := map[string]interface{}{
|
||||
"name": adminInfo["username"],
|
||||
"id": adminInfo["username"],
|
||||
"domain": "ns_server",
|
||||
"roles": []interface{}{
|
||||
map[string]interface{}{
|
||||
"role": "admin",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Add the configured administrator to the list of results.
|
||||
ret = append(ret, adminResult)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetUserInfoAll() ([]User, error) {
|
||||
ret := make([]User, 0, 16)
|
||||
err := c.parseURLResponse("/settings/rbac/users", &ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func rolesToParamFormat(roles []Role) string {
|
||||
var buffer bytes.Buffer
|
||||
for i, role := range roles {
|
||||
if i > 0 {
|
||||
buffer.WriteString(",")
|
||||
}
|
||||
buffer.WriteString(role.Role)
|
||||
if role.BucketName != "" {
|
||||
buffer.WriteString("[")
|
||||
buffer.WriteString(role.BucketName)
|
||||
buffer.WriteString("]")
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (c *Client) PutUserInfo(u *User) error {
|
||||
params := map[string]interface{}{
|
||||
"name": u.Name,
|
||||
"roles": rolesToParamFormat(u.Roles),
|
||||
}
|
||||
var target string
|
||||
switch u.Domain {
|
||||
case "external":
|
||||
target = "/settings/rbac/users/" + u.Id
|
||||
case "local":
|
||||
target = "/settings/rbac/users/local/" + u.Id
|
||||
default:
|
||||
return fmt.Errorf("Unknown user type: %s", u.Domain)
|
||||
}
|
||||
var ret string // PUT returns an empty string. We ignore it.
|
||||
err := c.parsePutURLResponse(target, params, &ret)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) GetRolesAll() ([]RoleDescription, error) {
|
||||
ret := make([]RoleDescription, 0, 32)
|
||||
err := c.parseURLResponse("/settings/rbac/roles", &ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ret, nil
|
||||
}
|
49
vendor/github.com/couchbaselabs/go-couchbase/util.go
generated
vendored
Normal file
49
vendor/github.com/couchbaselabs/go-couchbase/util.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CleanupHost returns the hostname with the given suffix removed.
|
||||
func CleanupHost(h, commonSuffix string) string {
|
||||
if strings.HasSuffix(h, commonSuffix) {
|
||||
return h[:len(h)-len(commonSuffix)]
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// FindCommonSuffix returns the longest common suffix from the given
|
||||
// strings.
|
||||
func FindCommonSuffix(input []string) string {
|
||||
rv := ""
|
||||
if len(input) < 2 {
|
||||
return ""
|
||||
}
|
||||
from := input
|
||||
for i := len(input[0]); i > 0; i-- {
|
||||
common := true
|
||||
suffix := input[0][i:]
|
||||
for _, s := range from {
|
||||
if !strings.HasSuffix(s, suffix) {
|
||||
common = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if common {
|
||||
rv = suffix
|
||||
}
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
// ParseURL is a wrapper around url.Parse with some sanity-checking
|
||||
func ParseURL(urlStr string) (result *url.URL, err error) {
|
||||
result, err = url.Parse(urlStr)
|
||||
if result != nil && result.Scheme == "" {
|
||||
result = nil
|
||||
err = fmt.Errorf("invalid URL <%s>", urlStr)
|
||||
}
|
||||
return
|
||||
}
|
77
vendor/github.com/couchbaselabs/go-couchbase/vbmap.go
generated
vendored
Normal file
77
vendor/github.com/couchbaselabs/go-couchbase/vbmap.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
||||
package couchbase
|
||||
|
||||
var crc32tab = []uint32{
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
|
||||
0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
|
||||
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
|
||||
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
||||
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
|
||||
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
||||
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
|
||||
0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
|
||||
0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
|
||||
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
|
||||
0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
|
||||
0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
||||
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
|
||||
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
|
||||
0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
||||
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
|
||||
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
|
||||
0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
|
||||
0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
|
||||
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
|
||||
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
|
||||
0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
|
||||
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
||||
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
|
||||
0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
|
||||
0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
|
||||
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
|
||||
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
|
||||
0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
||||
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
|
||||
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
|
||||
0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
||||
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
|
||||
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
|
||||
0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
|
||||
0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
|
||||
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}
|
||||
|
||||
// VBHash finds the vbucket for the given key.
|
||||
func (b *Bucket) VBHash(key string) uint32 {
|
||||
crc := uint32(0xffffffff)
|
||||
for x := 0; x < len(key); x++ {
|
||||
crc = (crc >> 8) ^ crc32tab[(uint64(crc)^uint64(key[x]))&0xff]
|
||||
}
|
||||
vbm := b.VBServerMap()
|
||||
return ((^crc) >> 16) & 0x7fff & (uint32(len(vbm.VBucketMap)) - 1)
|
||||
}
|
231
vendor/github.com/couchbaselabs/go-couchbase/views.go
generated
vendored
Normal file
231
vendor/github.com/couchbaselabs/go-couchbase/views.go
generated
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
package couchbase
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ViewRow represents a single result from a view.
|
||||
//
|
||||
// Doc is present only if include_docs was set on the request.
|
||||
type ViewRow struct {
|
||||
ID string
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
Doc *interface{}
|
||||
}
|
||||
|
||||
// A ViewError is a node-specific error indicating a partial failure
|
||||
// within a view result.
|
||||
type ViewError struct {
|
||||
From string
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (ve ViewError) Error() string {
|
||||
return "Node: " + ve.From + ", reason: " + ve.Reason
|
||||
}
|
||||
|
||||
// ViewResult holds the entire result set from a view request,
|
||||
// including the rows and the errors.
|
||||
type ViewResult struct {
|
||||
TotalRows int `json:"total_rows"`
|
||||
Rows []ViewRow
|
||||
Errors []ViewError
|
||||
}
|
||||
|
||||
func (b *Bucket) randomBaseURL() (*url.URL, error) {
|
||||
nodes := b.HealthyNodes()
|
||||
if len(nodes) == 0 {
|
||||
return nil, errors.New("no available couch rest URLs")
|
||||
}
|
||||
nodeNo := rand.Intn(len(nodes))
|
||||
node := nodes[nodeNo]
|
||||
|
||||
b.RLock()
|
||||
name := b.Name
|
||||
pool := b.pool
|
||||
b.RUnlock()
|
||||
|
||||
u, err := ParseURL(node.CouchAPIBase)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config error: Bucket %q node #%d CouchAPIBase=%q: %v",
|
||||
name, nodeNo, node.CouchAPIBase, err)
|
||||
} else if pool != nil {
|
||||
u.User = pool.client.BaseURL.User
|
||||
}
|
||||
return u, err
|
||||
}
|
||||
|
||||
const START_NODE_ID = -1
|
||||
|
||||
func (b *Bucket) randomNextURL(lastNode int) (*url.URL, int, error) {
|
||||
nodes := b.HealthyNodes()
|
||||
if len(nodes) == 0 {
|
||||
return nil, -1, errors.New("no available couch rest URLs")
|
||||
}
|
||||
|
||||
var nodeNo int
|
||||
if lastNode == START_NODE_ID || lastNode >= len(nodes) {
|
||||
// randomly select a node if the value of lastNode is invalid
|
||||
nodeNo = rand.Intn(len(nodes))
|
||||
} else {
|
||||
// wrap around the node list
|
||||
nodeNo = (lastNode + 1) % len(nodes)
|
||||
}
|
||||
|
||||
b.RLock()
|
||||
name := b.Name
|
||||
pool := b.pool
|
||||
b.RUnlock()
|
||||
|
||||
node := nodes[nodeNo]
|
||||
u, err := ParseURL(node.CouchAPIBase)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("config error: Bucket %q node #%d CouchAPIBase=%q: %v",
|
||||
name, nodeNo, node.CouchAPIBase, err)
|
||||
} else if pool != nil {
|
||||
u.User = pool.client.BaseURL.User
|
||||
}
|
||||
return u, nodeNo, err
|
||||
}
|
||||
|
||||
// DocID is the document ID type for the startkey_docid parameter in
|
||||
// views.
|
||||
type DocID string
|
||||
|
||||
func qParam(k, v string) string {
|
||||
format := `"%s"`
|
||||
switch k {
|
||||
case "startkey_docid", "endkey_docid", "stale":
|
||||
format = "%s"
|
||||
}
|
||||
return fmt.Sprintf(format, v)
|
||||
}
|
||||
|
||||
// ViewURL constructs a URL for a view with the given ddoc, view name,
|
||||
// and parameters.
|
||||
func (b *Bucket) ViewURL(ddoc, name string,
|
||||
params map[string]interface{}) (string, error) {
|
||||
u, err := b.randomBaseURL()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
values := url.Values{}
|
||||
for k, v := range params {
|
||||
switch t := v.(type) {
|
||||
case DocID:
|
||||
values[k] = []string{string(t)}
|
||||
case string:
|
||||
values[k] = []string{qParam(k, t)}
|
||||
case int:
|
||||
values[k] = []string{fmt.Sprintf(`%d`, t)}
|
||||
case bool:
|
||||
values[k] = []string{fmt.Sprintf(`%v`, t)}
|
||||
default:
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unsupported value-type %T in Query, "+
|
||||
"json encoder said %v", t, err)
|
||||
}
|
||||
values[k] = []string{fmt.Sprintf(`%v`, string(b))}
|
||||
}
|
||||
}
|
||||
|
||||
if ddoc == "" && name == "_all_docs" {
|
||||
u.Path = fmt.Sprintf("/%s/_all_docs", b.GetName())
|
||||
} else {
|
||||
u.Path = fmt.Sprintf("/%s/_design/%s/_view/%s", b.GetName(), ddoc, name)
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// ViewCallback is called for each view invocation.
|
||||
var ViewCallback func(ddoc, name string, start time.Time, err error)
|
||||
|
||||
// ViewCustom performs a view request that can map row values to a
|
||||
// custom type.
|
||||
//
|
||||
// See the source to View for an example usage.
|
||||
func (b *Bucket) ViewCustom(ddoc, name string, params map[string]interface{},
|
||||
vres interface{}) (err error) {
|
||||
if SlowServerCallWarningThreshold > 0 {
|
||||
defer slowLog(time.Now(), "call to ViewCustom(%q, %q)", ddoc, name)
|
||||
}
|
||||
|
||||
if ViewCallback != nil {
|
||||
defer func(t time.Time) { ViewCallback(ddoc, name, t, err) }(time.Now())
|
||||
}
|
||||
|
||||
u, err := b.ViewURL(ddoc, name, params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ah := b.authHandler(false /* bucket not yet locked */)
|
||||
maybeAddAuth(req, ah)
|
||||
|
||||
res, err := doHTTPRequest(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error starting view req at %v: %v", u, err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
bod := make([]byte, 512)
|
||||
l, _ := res.Body.Read(bod)
|
||||
return fmt.Errorf("error executing view req at %v: %v - %s",
|
||||
u, res.Status, bod[:l])
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err := json.Unmarshal(body, vres); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// View executes a view.
|
||||
//
|
||||
// The ddoc parameter is just the bare name of your design doc without
|
||||
// the "_design/" prefix.
|
||||
//
|
||||
// Parameters are string keys with values that correspond to couchbase
|
||||
// view parameters. Primitive should work fairly naturally (booleans,
|
||||
// ints, strings, etc...) and other values will attempt to be JSON
|
||||
// marshaled (useful for array indexing on on view keys, for example).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// res, err := couchbase.View("myddoc", "myview", map[string]interface{}{
|
||||
// "group_level": 2,
|
||||
// "startkey_docid": []interface{}{"thing"},
|
||||
// "endkey_docid": []interface{}{"thing", map[string]string{}},
|
||||
// "stale": false,
|
||||
// })
|
||||
func (b *Bucket) View(ddoc, name string, params map[string]interface{}) (ViewResult, error) {
|
||||
vres := ViewResult{}
|
||||
|
||||
if err := b.ViewCustom(ddoc, name, params, &vres); err != nil {
|
||||
//error in accessing views. Retry once after a bucket refresh
|
||||
b.Refresh()
|
||||
return vres, b.ViewCustom(ddoc, name, params, &vres)
|
||||
} else {
|
||||
return vres, nil
|
||||
}
|
||||
}
|
228
vendor/github.com/go-macaron/session/couchbase/couchbase.go
generated
vendored
Normal file
228
vendor/github.com/go-macaron/session/couchbase/couchbase.go
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/couchbaselabs/go-couchbase"
|
||||
|
||||
"github.com/go-macaron/session"
|
||||
)
|
||||
|
||||
// CouchbaseSessionStore represents a couchbase session store implementation.
|
||||
type CouchbaseSessionStore struct {
|
||||
b *couchbase.Bucket
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
maxlifetime int64
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *CouchbaseSessionStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *CouchbaseSessionStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *CouchbaseSessionStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *CouchbaseSessionStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *CouchbaseSessionStore) Release() error {
|
||||
defer s.b.Close()
|
||||
|
||||
// Skip encoding if the data is empty
|
||||
if len(s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.b.Set(s.sid, int(s.maxlifetime), data)
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *CouchbaseSessionStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// CouchbaseProvider represents a couchbase session provider implementation.
|
||||
type CouchbaseProvider struct {
|
||||
maxlifetime int64
|
||||
connStr string
|
||||
pool string
|
||||
bucket string
|
||||
b *couchbase.Bucket
|
||||
}
|
||||
|
||||
func (cp *CouchbaseProvider) getBucket() *couchbase.Bucket {
|
||||
c, err := couchbase.Connect(cp.connStr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pool, err := c.GetPool(cp.pool)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
bucket, err := pool.GetBucket(cp.bucket)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bucket
|
||||
}
|
||||
|
||||
// Init initializes memory session provider.
|
||||
// connStr is couchbase server REST/JSON URL
|
||||
// e.g. http://host:port/, Pool, Bucket
|
||||
func (p *CouchbaseProvider) Init(maxlifetime int64, connStr string) error {
|
||||
p.maxlifetime = maxlifetime
|
||||
configs := strings.Split(connStr, ",")
|
||||
if len(configs) > 0 {
|
||||
p.connStr = configs[0]
|
||||
}
|
||||
if len(configs) > 1 {
|
||||
p.pool = configs[1]
|
||||
}
|
||||
if len(configs) > 2 {
|
||||
p.bucket = configs[2]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *CouchbaseProvider) Read(sid string) (session.RawStore, error) {
|
||||
p.b = p.getBucket()
|
||||
|
||||
var doc []byte
|
||||
|
||||
err := p.b.Get(sid, &doc)
|
||||
var kv map[interface{}]interface{}
|
||||
if doc == nil {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime}
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *CouchbaseProvider) Exist(sid string) bool {
|
||||
p.b = p.getBucket()
|
||||
defer p.b.Close()
|
||||
|
||||
var doc []byte
|
||||
|
||||
if err := p.b.Get(sid, &doc); err != nil || doc == nil {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *CouchbaseProvider) Destory(sid string) error {
|
||||
p.b = p.getBucket()
|
||||
defer p.b.Close()
|
||||
|
||||
p.b.Delete(sid)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *CouchbaseProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
|
||||
p.b = p.getBucket()
|
||||
|
||||
var doc []byte
|
||||
if err := p.b.Get(oldsid, &doc); err != nil || doc == nil {
|
||||
p.b.Set(sid, int(p.maxlifetime), "")
|
||||
} else {
|
||||
err := p.b.Delete(oldsid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _ = p.b.Add(sid, int(p.maxlifetime), doc)
|
||||
}
|
||||
|
||||
err := p.b.Get(sid, &doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var kv map[interface{}]interface{}
|
||||
if doc == nil {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(doc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime}
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *CouchbaseProvider) Count() int {
|
||||
// FIXME
|
||||
return 0
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *CouchbaseProvider) GC() {}
|
||||
|
||||
func init() {
|
||||
session.Register("couchbase", &CouchbaseProvider{})
|
||||
}
|
5
vendor/github.com/go-macaron/session/file.go
generated
vendored
5
vendor/github.com/go-macaron/session/file.go
generated
vendored
@ -81,6 +81,11 @@ func (s *FileStore) Release() error {
|
||||
s.p.lock.Lock()
|
||||
defer s.p.lock.Unlock()
|
||||
|
||||
// Skip encoding if the data is empty
|
||||
if len(s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
|
61
vendor/github.com/go-macaron/session/flash.go
generated
vendored
Normal file
61
vendor/github.com/go-macaron/session/flash.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2018 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
type Flash struct {
|
||||
ctx *macaron.Context
|
||||
url.Values
|
||||
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
|
||||
}
|
||||
|
||||
func (f *Flash) set(name, msg string, current ...bool) {
|
||||
isShow := false
|
||||
if (len(current) == 0 && macaron.FlashNow) ||
|
||||
(len(current) > 0 && current[0]) {
|
||||
isShow = true
|
||||
}
|
||||
|
||||
if isShow {
|
||||
f.ctx.Data["Flash"] = f
|
||||
} else {
|
||||
f.Set(name, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flash) Error(msg string, current ...bool) {
|
||||
f.ErrorMsg = msg
|
||||
f.set("error", msg, current...)
|
||||
}
|
||||
|
||||
func (f *Flash) Warning(msg string, current ...bool) {
|
||||
f.WarningMsg = msg
|
||||
f.set("warning", msg, current...)
|
||||
}
|
||||
|
||||
func (f *Flash) Info(msg string, current ...bool) {
|
||||
f.InfoMsg = msg
|
||||
f.set("info", msg, current...)
|
||||
}
|
||||
|
||||
func (f *Flash) Success(msg string, current ...bool) {
|
||||
f.SuccessMsg = msg
|
||||
f.set("success", msg, current...)
|
||||
}
|
204
vendor/github.com/go-macaron/session/memcache/memcache.go
generated
vendored
Normal file
204
vendor/github.com/go-macaron/session/memcache/memcache.go
generated
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/bradfitz/gomemcache/memcache"
|
||||
|
||||
"github.com/go-macaron/session"
|
||||
)
|
||||
|
||||
// MemcacheStore represents a memcache session store implementation.
|
||||
type MemcacheStore struct {
|
||||
c *memcache.Client
|
||||
sid string
|
||||
expire int32
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewMemcacheStore creates and returns a memcache session store.
|
||||
func NewMemcacheStore(c *memcache.Client, sid string, expire int32, kv map[interface{}]interface{}) *MemcacheStore {
|
||||
return &MemcacheStore{
|
||||
c: c,
|
||||
sid: sid,
|
||||
expire: expire,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
func NewItem(sid string, data []byte, expire int32) *memcache.Item {
|
||||
return &memcache.Item{
|
||||
Key: sid,
|
||||
Value: data,
|
||||
Expiration: expire,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *MemcacheStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *MemcacheStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *MemcacheStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *MemcacheStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *MemcacheStore) Release() error {
|
||||
// Skip encoding if the data is empty
|
||||
if len(s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.c.Set(NewItem(s.sid, data, s.expire))
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *MemcacheStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// MemcacheProvider represents a memcache session provider implementation.
|
||||
type MemcacheProvider struct {
|
||||
c *memcache.Client
|
||||
expire int32
|
||||
}
|
||||
|
||||
// Init initializes memcache session provider.
|
||||
// connStrs: 127.0.0.1:9090;127.0.0.1:9091
|
||||
func (p *MemcacheProvider) Init(expire int64, connStrs string) error {
|
||||
p.expire = int32(expire)
|
||||
p.c = memcache.New(strings.Split(connStrs, ";")...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *MemcacheProvider) Read(sid string) (session.RawStore, error) {
|
||||
if !p.Exist(sid) {
|
||||
if err := p.c.Set(NewItem(sid, []byte(""), p.expire)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
item, err := p.c.Get(sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(item.Value) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(item.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewMemcacheStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *MemcacheProvider) Exist(sid string) bool {
|
||||
_, err := p.c.Get(sid)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *MemcacheProvider) Destory(sid string) error {
|
||||
return p.c.Delete(sid)
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *MemcacheProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
|
||||
item := NewItem(sid, []byte(""), p.expire)
|
||||
if p.Exist(oldsid) {
|
||||
item, err = p.c.Get(oldsid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if err = p.c.Delete(oldsid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Key = sid
|
||||
}
|
||||
if err = p.c.Set(item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(item.Value) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(item.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewMemcacheStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *MemcacheProvider) Count() int {
|
||||
// FIXME: how come this library does not have Stats method?
|
||||
return -1
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *MemcacheProvider) GC() {}
|
||||
|
||||
func init() {
|
||||
session.Register("memcache", &MemcacheProvider{})
|
||||
}
|
200
vendor/github.com/go-macaron/session/mysql/mysql.go
generated
vendored
Normal file
200
vendor/github.com/go-macaron/session/mysql/mysql.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
|
||||
"github.com/go-macaron/session"
|
||||
)
|
||||
|
||||
// MysqlStore represents a mysql session store implementation.
|
||||
type MysqlStore struct {
|
||||
c *sql.DB
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewMysqlStore creates and returns a mysql session store.
|
||||
func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore {
|
||||
return &MysqlStore{
|
||||
c: c,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *MysqlStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *MysqlStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *MysqlStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *MysqlStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *MysqlStore) Release() error {
|
||||
// Skip encoding if the data is empty
|
||||
if len(s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?",
|
||||
data, time.Now().Unix(), s.sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *MysqlStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// MysqlProvider represents a mysql session provider implementation.
|
||||
type MysqlProvider struct {
|
||||
c *sql.DB
|
||||
expire int64
|
||||
}
|
||||
|
||||
// Init initializes mysql session provider.
|
||||
// connStr: username:password@protocol(address)/dbname?param=value
|
||||
func (p *MysqlProvider) Init(expire int64, connStr string) (err error) {
|
||||
p.expire = expire
|
||||
|
||||
p.c, err = sql.Open("mysql", connStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.c.Ping()
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *MysqlProvider) Read(sid string) (session.RawStore, error) {
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
|
||||
if err == sql.ErrNoRows {
|
||||
_, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
|
||||
sid, "", time.Now().Unix())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(data) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewMysqlStore(p.c, sid, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *MysqlProvider) Exist(sid string) bool {
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
panic("session/mysql: error checking existence: " + err.Error())
|
||||
}
|
||||
return err != sql.ErrNoRows
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *MysqlProvider) Destory(sid string) error {
|
||||
_, err := p.c.Exec("DELETE FROM session WHERE `key`=?", sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *MysqlProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
|
||||
if !p.Exist(oldsid) {
|
||||
if _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)",
|
||||
oldsid, "", time.Now().Unix()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = p.c.Exec("UPDATE session SET `key`=? WHERE `key`=?", sid, oldsid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.Read(sid)
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *MysqlProvider) Count() (total int) {
|
||||
if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
|
||||
panic("session/mysql: error counting records: " + err.Error())
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *MysqlProvider) GC() {
|
||||
if _, err := p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire); err != nil {
|
||||
log.Printf("session/mysql: error garbage collecting: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
session.Register("mysql", &MysqlProvider{})
|
||||
}
|
208
vendor/github.com/go-macaron/session/nodb/nodb.go
generated
vendored
Normal file
208
vendor/github.com/go-macaron/session/nodb/nodb.go
generated
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
// Copyright 2015 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/lunny/nodb"
|
||||
"github.com/lunny/nodb/config"
|
||||
|
||||
"github.com/go-macaron/session"
|
||||
)
|
||||
|
||||
// NodbStore represents a nodb session store implementation.
|
||||
type NodbStore struct {
|
||||
c *nodb.DB
|
||||
sid string
|
||||
expire int64
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewNodbStore creates and returns a ledis session store.
|
||||
func NewNodbStore(c *nodb.DB, sid string, expire int64, kv map[interface{}]interface{}) *NodbStore {
|
||||
return &NodbStore{
|
||||
c: c,
|
||||
expire: expire,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *NodbStore) Set(key, val interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *NodbStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *NodbStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *NodbStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *NodbStore) Release() error {
|
||||
// Skip encoding if the data is empty
|
||||
if len(s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.c.Set([]byte(s.sid), data); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.c.Expire([]byte(s.sid), s.expire)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *NodbStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodbProvider represents a ledis session provider implementation.
|
||||
type NodbProvider struct {
|
||||
c *nodb.DB
|
||||
expire int64
|
||||
}
|
||||
|
||||
// Init initializes nodb session provider.
|
||||
func (p *NodbProvider) Init(expire int64, configs string) error {
|
||||
p.expire = expire
|
||||
|
||||
cfg := new(config.Config)
|
||||
cfg.DataDir = configs
|
||||
dbs, err := nodb.Open(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("session/nodb: error opening db: %v", err)
|
||||
}
|
||||
|
||||
p.c, err = dbs.Select(0)
|
||||
return err
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *NodbProvider) Read(sid string) (session.RawStore, error) {
|
||||
if !p.Exist(sid) {
|
||||
if err := p.c.Set([]byte(sid), []byte("")); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
kvs, err := p.c.Get([]byte(sid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(kvs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewNodbStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *NodbProvider) Exist(sid string) bool {
|
||||
count, err := p.c.Exists([]byte(sid))
|
||||
return err == nil && count > 0
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *NodbProvider) Destory(sid string) error {
|
||||
_, err := p.c.Del([]byte(sid))
|
||||
return err
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *NodbProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
|
||||
kvs := make([]byte, 0)
|
||||
if p.Exist(oldsid) {
|
||||
if kvs, err = p.c.Get([]byte(oldsid)); err != nil {
|
||||
return nil, err
|
||||
} else if _, err = p.c.Del([]byte(oldsid)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = p.c.Set([]byte(sid), kvs); err != nil {
|
||||
return nil, err
|
||||
} else if _, err = p.c.Expire([]byte(sid), p.expire); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(kvs) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob([]byte(kvs))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewNodbStore(p.c, sid, p.expire, kv), nil
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *NodbProvider) Count() int {
|
||||
// FIXME: how come this library does not have DbSize() method?
|
||||
return -1
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *NodbProvider) GC() {}
|
||||
|
||||
func init() {
|
||||
session.Register("nodb", &NodbProvider{})
|
||||
}
|
201
vendor/github.com/go-macaron/session/postgres/postgres.go
generated
vendored
Normal file
201
vendor/github.com/go-macaron/session/postgres/postgres.go
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||
// not use this file except in compliance with the License. You may obtain
|
||||
// a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/go-macaron/session"
|
||||
)
|
||||
|
||||
// PostgresStore represents a postgres session store implementation.
|
||||
type PostgresStore struct {
|
||||
c *sql.DB
|
||||
sid string
|
||||
lock sync.RWMutex
|
||||
data map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewPostgresStore creates and returns a postgres session store.
|
||||
func NewPostgresStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *PostgresStore {
|
||||
return &PostgresStore{
|
||||
c: c,
|
||||
sid: sid,
|
||||
data: kv,
|
||||
}
|
||||
}
|
||||
|
||||
// Set sets value to given key in session.
|
||||
func (s *PostgresStore) Set(key, value interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets value by given key in session.
|
||||
func (s *PostgresStore) Get(key interface{}) interface{} {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return s.data[key]
|
||||
}
|
||||
|
||||
// Delete delete a key from session.
|
||||
func (s *PostgresStore) Delete(key interface{}) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.data, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID returns current session ID.
|
||||
func (s *PostgresStore) ID() string {
|
||||
return s.sid
|
||||
}
|
||||
|
||||
// save postgres session values to database.
|
||||
// must call this method to save values to database.
|
||||
func (s *PostgresStore) Release() error {
|
||||
// Skip encoding if the data is empty
|
||||
if len(s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.c.Exec("UPDATE session SET data=$1, expiry=$2 WHERE key=$3",
|
||||
data, time.Now().Unix(), s.sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush deletes all session data.
|
||||
func (s *PostgresStore) Flush() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.data = make(map[interface{}]interface{})
|
||||
return nil
|
||||
}
|
||||
|
||||
// PostgresProvider represents a postgres session provider implementation.
|
||||
type PostgresProvider struct {
|
||||
c *sql.DB
|
||||
maxlifetime int64
|
||||
}
|
||||
|
||||
// Init initializes postgres session provider.
|
||||
// connStr: user=a password=b host=localhost port=5432 dbname=c sslmode=disable
|
||||
func (p *PostgresProvider) Init(maxlifetime int64, connStr string) (err error) {
|
||||
p.maxlifetime = maxlifetime
|
||||
|
||||
p.c, err = sql.Open("postgres", connStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.c.Ping()
|
||||
}
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (p *PostgresProvider) Read(sid string) (session.RawStore, error) {
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
|
||||
if err == sql.ErrNoRows {
|
||||
_, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
|
||||
sid, "", time.Now().Unix())
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kv map[interface{}]interface{}
|
||||
if len(data) == 0 {
|
||||
kv = make(map[interface{}]interface{})
|
||||
} else {
|
||||
kv, err = session.DecodeGob(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewPostgresStore(p.c, sid, kv), nil
|
||||
}
|
||||
|
||||
// Exist returns true if session with given ID exists.
|
||||
func (p *PostgresProvider) Exist(sid string) bool {
|
||||
var data []byte
|
||||
err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
panic("session/postgres: error checking existence: " + err.Error())
|
||||
}
|
||||
return err != sql.ErrNoRows
|
||||
}
|
||||
|
||||
// Destory deletes a session by session ID.
|
||||
func (p *PostgresProvider) Destory(sid string) error {
|
||||
_, err := p.c.Exec("DELETE FROM session WHERE key=$1", sid)
|
||||
return err
|
||||
}
|
||||
|
||||
// Regenerate regenerates a session store from old session ID to new one.
|
||||
func (p *PostgresProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) {
|
||||
if p.Exist(sid) {
|
||||
return nil, fmt.Errorf("new sid '%s' already exists", sid)
|
||||
}
|
||||
|
||||
if !p.Exist(oldsid) {
|
||||
if _, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)",
|
||||
oldsid, "", time.Now().Unix()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = p.c.Exec("UPDATE session SET key=$1 WHERE key=$2", sid, oldsid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.Read(sid)
|
||||
}
|
||||
|
||||
// Count counts and returns number of sessions.
|
||||
func (p *PostgresProvider) Count() (total int) {
|
||||
if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil {
|
||||
panic("session/postgres: error counting records: " + err.Error())
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// GC calls GC to clean expired sessions.
|
||||
func (p *PostgresProvider) GC() {
|
||||
if _, err := p.c.Exec("DELETE FROM session WHERE EXTRACT(EPOCH FROM NOW()) - expiry > $1", p.maxlifetime); err != nil {
|
||||
log.Printf("session/postgres: error garbage collecting: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
session.Register("postgres", &PostgresProvider{})
|
||||
}
|
7
vendor/github.com/go-macaron/session/redis/redis.go
generated
vendored
7
vendor/github.com/go-macaron/session/redis/redis.go
generated
vendored
@ -81,6 +81,11 @@ func (s *RedisStore) ID() string {
|
||||
|
||||
// Release releases resource and save data to provider.
|
||||
func (s *RedisStore) Release() error {
|
||||
// Skip encoding if the data is empty
|
||||
if len(s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := session.EncodeGob(s.data)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -153,7 +158,7 @@ func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) {
|
||||
func (p *RedisProvider) Read(sid string) (session.RawStore, error) {
|
||||
psid := p.prefix + sid
|
||||
if !p.Exist(sid) {
|
||||
if err := p.c.Set(psid, "").Err(); err != nil {
|
||||
if err := p.c.SetEx(psid, p.duration, "").Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
104
vendor/github.com/go-macaron/session/session.go
generated
vendored
104
vendor/github.com/go-macaron/session/session.go
generated
vendored
@ -22,13 +22,12 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
const _VERSION = "0.4.0"
|
||||
const _VERSION = "0.6.0"
|
||||
|
||||
func Version() string {
|
||||
return _VERSION
|
||||
@ -96,6 +95,8 @@ type Options struct {
|
||||
IDLength int
|
||||
// Configuration section name. Default is "session".
|
||||
Section string
|
||||
// Ignore release for websocket. Default is false.
|
||||
IgnoreReleaseForWebSocket bool
|
||||
}
|
||||
|
||||
func prepareOptions(options []Options) Options {
|
||||
@ -138,6 +139,9 @@ func prepareOptions(options []Options) Options {
|
||||
if opt.IDLength == 0 {
|
||||
opt.IDLength = sec.Key("ID_LENGTH").MustInt(16)
|
||||
}
|
||||
if !opt.IgnoreReleaseForWebSocket {
|
||||
opt.IgnoreReleaseForWebSocket = sec.Key("IGNORE_RELEASE_FOR_WEBSOCKET").MustBool()
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
@ -187,6 +191,10 @@ func Sessioner(options ...Options) macaron.Handler {
|
||||
|
||||
ctx.Next()
|
||||
|
||||
if manager.opt.IgnoreReleaseForWebSocket && ctx.Req.Header.Get("Upgrade") == "websocket" {
|
||||
return
|
||||
}
|
||||
|
||||
if err = sess.Release(); err != nil {
|
||||
panic("session(release): " + err.Error())
|
||||
}
|
||||
@ -252,12 +260,30 @@ func (m *Manager) sessionID() string {
|
||||
return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2))
|
||||
}
|
||||
|
||||
// validSessionID tests whether a provided session ID is a valid session ID.
|
||||
func (m *Manager) validSessionID(sid string) (bool, error) {
|
||||
if len(sid) != m.opt.IDLength {
|
||||
return false, errors.New("invalid 'sid': " + sid)
|
||||
}
|
||||
|
||||
for i := range sid {
|
||||
switch {
|
||||
case '0' <= sid[i] && sid[i] <= '9':
|
||||
case 'a' <= sid[i] && sid[i] <= 'f':
|
||||
default:
|
||||
return false, errors.New("invalid 'sid': " + sid)
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Start starts a session by generating new one
|
||||
// or retrieve existence one by reading session ID from HTTP request if it's valid.
|
||||
func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) {
|
||||
sid := ctx.GetCookie(m.opt.CookieName)
|
||||
if len(sid) > 0 && m.provider.Exist(sid) {
|
||||
return m.Read(sid)
|
||||
valid, _ := m.validSessionID(sid)
|
||||
if len(sid) > 0 && valid && m.provider.Exist(sid) {
|
||||
return m.provider.Read(sid)
|
||||
}
|
||||
|
||||
sid = m.sessionID()
|
||||
@ -284,10 +310,9 @@ func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) {
|
||||
|
||||
// Read returns raw session store by session ID.
|
||||
func (m *Manager) Read(sid string) (RawStore, error) {
|
||||
// No slashes or dots "./" should ever occur in the sid and to prevent session file forgery bug.
|
||||
// See https://github.com/gogs/gogs/issues/5469
|
||||
if strings.ContainsAny(sid, "./") {
|
||||
return nil, errors.New("invalid 'sid': " + sid)
|
||||
// Ensure we're trying to read a valid session ID
|
||||
if _, err := m.validSessionID(sid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m.provider.Read(sid)
|
||||
@ -300,6 +325,10 @@ func (m *Manager) Destory(ctx *macaron.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := m.validSessionID(sid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.provider.Destory(sid); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -318,11 +347,15 @@ func (m *Manager) Destory(ctx *macaron.Context) error {
|
||||
func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) {
|
||||
sid := m.sessionID()
|
||||
oldsid := ctx.GetCookie(m.opt.CookieName)
|
||||
_, err = m.validSessionID(oldsid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sess, err = m.provider.Regenerate(oldsid, sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ck := &http.Cookie{
|
||||
cookie := &http.Cookie{
|
||||
Name: m.opt.CookieName,
|
||||
Value: sid,
|
||||
Path: m.opt.CookiePath,
|
||||
@ -331,10 +364,10 @@ func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error)
|
||||
Domain: m.opt.Domain,
|
||||
}
|
||||
if m.opt.CookieLifeTime >= 0 {
|
||||
ck.MaxAge = m.opt.CookieLifeTime
|
||||
cookie.MaxAge = m.opt.CookieLifeTime
|
||||
}
|
||||
http.SetCookie(ctx.Resp, ck)
|
||||
ctx.Req.AddCookie(ck)
|
||||
http.SetCookie(ctx.Resp, cookie)
|
||||
ctx.Req.AddCookie(cookie)
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
@ -358,50 +391,3 @@ func (m *Manager) startGC() {
|
||||
func (m *Manager) SetSecure(secure bool) {
|
||||
m.opt.Secure = secure
|
||||
}
|
||||
|
||||
// ___________.____ _____ _________ ___ ___
|
||||
// \_ _____/| | / _ \ / _____// | \
|
||||
// | __) | | / /_\ \ \_____ \/ ~ \
|
||||
// | \ | |___/ | \/ \ Y /
|
||||
// \___ / |_______ \____|__ /_______ /\___|_ /
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type Flash struct {
|
||||
ctx *macaron.Context
|
||||
url.Values
|
||||
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
|
||||
}
|
||||
|
||||
func (f *Flash) set(name, msg string, current ...bool) {
|
||||
isShow := false
|
||||
if (len(current) == 0 && macaron.FlashNow) ||
|
||||
(len(current) > 0 && current[0]) {
|
||||
isShow = true
|
||||
}
|
||||
|
||||
if isShow {
|
||||
f.ctx.Data["Flash"] = f
|
||||
} else {
|
||||
f.Set(name, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Flash) Error(msg string, current ...bool) {
|
||||
f.ErrorMsg = msg
|
||||
f.set("error", msg, current...)
|
||||
}
|
||||
|
||||
func (f *Flash) Warning(msg string, current ...bool) {
|
||||
f.WarningMsg = msg
|
||||
f.set("warning", msg, current...)
|
||||
}
|
||||
|
||||
func (f *Flash) Info(msg string, current ...bool) {
|
||||
f.InfoMsg = msg
|
||||
f.set("info", msg, current...)
|
||||
}
|
||||
|
||||
func (f *Flash) Success(msg string, current ...bool) {
|
||||
f.SuccessMsg = msg
|
||||
f.set("success", msg, current...)
|
||||
}
|
||||
|
5
vendor/github.com/go-macaron/session/utils.go
generated
vendored
5
vendor/github.com/go-macaron/session/utils.go
generated
vendored
@ -50,11 +50,14 @@ func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) {
|
||||
return out, err
|
||||
}
|
||||
|
||||
// NOTE: A local copy in case of underlying package change
|
||||
var alphanum = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
|
||||
|
||||
// generateRandomKey creates a random key with the given strength.
|
||||
func generateRandomKey(strength int) []byte {
|
||||
k := make([]byte, strength)
|
||||
if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil {
|
||||
return com.RandomCreateBytes(strength)
|
||||
return com.RandomCreateBytes(strength, alphanum...)
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
27
vendor/github.com/lunny/log/LICENSE
generated
vendored
Normal file
27
vendor/github.com/lunny/log/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2014 - 2016 lunny
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the {organization} nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
36
vendor/github.com/lunny/log/dbwriter.go
generated
vendored
Normal file
36
vendor/github.com/lunny/log/dbwriter.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DBWriter struct {
|
||||
db *sql.DB
|
||||
stmt *sql.Stmt
|
||||
content chan []byte
|
||||
}
|
||||
|
||||
func NewDBWriter(db *sql.DB) (*DBWriter, error) {
|
||||
_, err := db.Exec("CREATE TABLE IF NOT EXISTS log (id int, content text, created datetime)")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stmt, err := db.Prepare("INSERT INTO log (content, created) values (?, ?)")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DBWriter{db, stmt, make(chan []byte, 1000)}, nil
|
||||
}
|
||||
|
||||
func (w *DBWriter) Write(p []byte) (n int, err error) {
|
||||
_, err = w.stmt.Exec(string(p), time.Now())
|
||||
if err == nil {
|
||||
n = len(p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *DBWriter) Close() {
|
||||
w.stmt.Close()
|
||||
}
|
112
vendor/github.com/lunny/log/filewriter.go
generated
vendored
Normal file
112
vendor/github.com/lunny/log/filewriter.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ io.Writer = &Files{}
|
||||
|
||||
type ByType int
|
||||
|
||||
const (
|
||||
ByDay ByType = iota
|
||||
ByHour
|
||||
ByMonth
|
||||
)
|
||||
|
||||
var (
|
||||
formats = map[ByType]string{
|
||||
ByDay: "2006-01-02",
|
||||
ByHour: "2006-01-02-15",
|
||||
ByMonth: "2006-01",
|
||||
}
|
||||
)
|
||||
|
||||
func SetFileFormat(t ByType, format string) {
|
||||
formats[t] = format
|
||||
}
|
||||
|
||||
func (b ByType) Format() string {
|
||||
return formats[b]
|
||||
}
|
||||
|
||||
type Files struct {
|
||||
FileOptions
|
||||
f *os.File
|
||||
lastFormat string
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
type FileOptions struct {
|
||||
Dir string
|
||||
ByType ByType
|
||||
Loc *time.Location
|
||||
}
|
||||
|
||||
func prepareFileOption(opts []FileOptions) FileOptions {
|
||||
var opt FileOptions
|
||||
if len(opts) > 0 {
|
||||
opt = opts[0]
|
||||
}
|
||||
if opt.Dir == "" {
|
||||
opt.Dir = "./"
|
||||
}
|
||||
err := os.MkdirAll(opt.Dir, os.ModePerm)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
if opt.Loc == nil {
|
||||
opt.Loc = time.Local
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
func NewFileWriter(opts ...FileOptions) *Files {
|
||||
opt := prepareFileOption(opts)
|
||||
return &Files{
|
||||
FileOptions: opt,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Files) getFile() (*os.File, error) {
|
||||
var err error
|
||||
t := time.Now().In(f.Loc)
|
||||
if f.f == nil {
|
||||
f.lastFormat = t.Format(f.ByType.Format())
|
||||
f.f, err = os.OpenFile(filepath.Join(f.Dir, f.lastFormat+".log"),
|
||||
os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
return f.f, err
|
||||
}
|
||||
if f.lastFormat != t.Format(f.ByType.Format()) {
|
||||
f.f.Close()
|
||||
f.lastFormat = t.Format(f.ByType.Format())
|
||||
f.f, err = os.OpenFile(filepath.Join(f.Dir, f.lastFormat+".log"),
|
||||
os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
|
||||
return f.f, err
|
||||
}
|
||||
return f.f, nil
|
||||
}
|
||||
|
||||
func (f *Files) Write(bs []byte) (int, error) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
w, err := f.getFile()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return w.Write(bs)
|
||||
}
|
||||
|
||||
func (f *Files) Close() {
|
||||
if f.f != nil {
|
||||
f.f.Close()
|
||||
f.f = nil
|
||||
}
|
||||
f.lastFormat = ""
|
||||
}
|
595
vendor/github.com/lunny/log/logext.go
generated
vendored
Normal file
595
vendor/github.com/lunny/log/logext.go
generated
vendored
Normal file
@ -0,0 +1,595 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// These flags define which text to prefix to each log entry generated by the Logger.
|
||||
const (
|
||||
// Bits or'ed together to control what's printed. There is no control over the
|
||||
// order they appear (the order listed here) or the format they present (as
|
||||
// described in the comments). A colon appears after these items:
|
||||
// 2009/0123 01:23:23.123123 /a/b/c/d.go:23: message
|
||||
Ldate = 1 << iota // the date: 2009/0123
|
||||
Ltime // the time: 01:23:23
|
||||
Lmicroseconds // microsecond resolution: 01:23:23.123123. assumes Ltime.
|
||||
Llongfile // full file name and line number: /a/b/c/d.go:23
|
||||
Lshortfile // final file name element and line number: d.go:23. overrides Llongfile
|
||||
Lmodule // module name
|
||||
Llevel // level: 0(Debug), 1(Info), 2(Warn), 3(Error), 4(Panic), 5(Fatal)
|
||||
Llongcolor // color will start [info] end of line
|
||||
Lshortcolor // color only include [info]
|
||||
LstdFlags = Ldate | Ltime // initial values for the standard logger
|
||||
//Ldefault = Llevel | LstdFlags | Lshortfile | Llongcolor
|
||||
) // [prefix][time][level][module][shortfile|longfile]
|
||||
|
||||
func Ldefault() int {
|
||||
if runtime.GOOS == "windows" {
|
||||
return Llevel | LstdFlags | Lshortfile
|
||||
}
|
||||
return Llevel | LstdFlags | Lshortfile | Llongcolor
|
||||
}
|
||||
|
||||
func Version() string {
|
||||
return "0.2.0.1121"
|
||||
}
|
||||
|
||||
const (
|
||||
Lall = iota
|
||||
)
|
||||
const (
|
||||
Ldebug = iota
|
||||
Linfo
|
||||
Lwarn
|
||||
Lerror
|
||||
Lpanic
|
||||
Lfatal
|
||||
Lnone
|
||||
)
|
||||
|
||||
const (
|
||||
ForeBlack = iota + 30 //30
|
||||
ForeRed //31
|
||||
ForeGreen //32
|
||||
ForeYellow //33
|
||||
ForeBlue //34
|
||||
ForePurple //35
|
||||
ForeCyan //36
|
||||
ForeWhite //37
|
||||
)
|
||||
|
||||
const (
|
||||
BackBlack = iota + 40 //40
|
||||
BackRed //41
|
||||
BackGreen //42
|
||||
BackYellow //43
|
||||
BackBlue //44
|
||||
BackPurple //45
|
||||
BackCyan //46
|
||||
BackWhite //47
|
||||
)
|
||||
|
||||
var levels = []string{
|
||||
"[Debug]",
|
||||
"[Info]",
|
||||
"[Warn]",
|
||||
"[Error]",
|
||||
"[Panic]",
|
||||
"[Fatal]",
|
||||
}
|
||||
|
||||
// MUST called before all logs
|
||||
func SetLevels(lvs []string) {
|
||||
levels = lvs
|
||||
}
|
||||
|
||||
var colors = []int{
|
||||
ForeCyan,
|
||||
ForeGreen,
|
||||
ForeYellow,
|
||||
ForeRed,
|
||||
ForePurple,
|
||||
ForeBlue,
|
||||
}
|
||||
|
||||
// MUST called before all logs
|
||||
func SetColors(cls []int) {
|
||||
colors = cls
|
||||
}
|
||||
|
||||
// A Logger represents an active logging object that generates lines of
|
||||
// output to an io.Writer. Each logging operation makes a single call to
|
||||
// the Writer's Write method. A Logger can be used simultaneously from
|
||||
// multiple goroutines; it guarantees to serialize access to the Writer.
|
||||
type Logger struct {
|
||||
mu sync.Mutex // ensures atomic writes; protects the following fields
|
||||
prefix string // prefix to write at beginning of each line
|
||||
flag int // properties
|
||||
Level int
|
||||
out io.Writer // destination for output
|
||||
buf bytes.Buffer // for accumulating text to write
|
||||
levelStats [6]int64
|
||||
loc *time.Location
|
||||
}
|
||||
|
||||
// New creates a new Logger. The out variable sets the
|
||||
// destination to which log data will be written.
|
||||
// The prefix appears at the beginning of each generated log line.
|
||||
// The flag argument defines the logging properties.
|
||||
func New(out io.Writer, prefix string, flag int) *Logger {
|
||||
l := &Logger{out: out, prefix: prefix, Level: 1, flag: flag, loc: time.Local}
|
||||
if out != os.Stdout {
|
||||
l.flag = RmColorFlags(l.flag)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
var Std = New(os.Stderr, "", Ldefault())
|
||||
|
||||
// Cheap integer to fixed-width decimal ASCII. Give a negative width to avoid zero-padding.
|
||||
// Knows the buffer has capacity.
|
||||
func itoa(buf *bytes.Buffer, i int, wid int) {
|
||||
var u uint = uint(i)
|
||||
if u == 0 && wid <= 1 {
|
||||
buf.WriteByte('0')
|
||||
return
|
||||
}
|
||||
|
||||
// Assemble decimal in reverse order.
|
||||
var b [32]byte
|
||||
bp := len(b)
|
||||
for ; u > 0 || wid > 0; u /= 10 {
|
||||
bp--
|
||||
wid--
|
||||
b[bp] = byte(u%10) + '0'
|
||||
}
|
||||
|
||||
// avoid slicing b to avoid an allocation.
|
||||
for bp < len(b) {
|
||||
buf.WriteByte(b[bp])
|
||||
bp++
|
||||
}
|
||||
}
|
||||
|
||||
func moduleOf(file string) string {
|
||||
pos := strings.LastIndex(file, "/")
|
||||
if pos != -1 {
|
||||
pos1 := strings.LastIndex(file[:pos], "/src/")
|
||||
if pos1 != -1 {
|
||||
return file[pos1+5 : pos]
|
||||
}
|
||||
}
|
||||
return "UNKNOWN"
|
||||
}
|
||||
|
||||
func (l *Logger) formatHeader(buf *bytes.Buffer, t time.Time,
|
||||
file string, line int, lvl int, reqId string) {
|
||||
if l.prefix != "" {
|
||||
buf.WriteString(l.prefix)
|
||||
}
|
||||
if l.flag&(Ldate|Ltime|Lmicroseconds) != 0 {
|
||||
if l.flag&Ldate != 0 {
|
||||
year, month, day := t.Date()
|
||||
itoa(buf, year, 4)
|
||||
buf.WriteByte('/')
|
||||
itoa(buf, int(month), 2)
|
||||
buf.WriteByte('/')
|
||||
itoa(buf, day, 2)
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
if l.flag&(Ltime|Lmicroseconds) != 0 {
|
||||
hour, min, sec := t.Clock()
|
||||
itoa(buf, hour, 2)
|
||||
buf.WriteByte(':')
|
||||
itoa(buf, min, 2)
|
||||
buf.WriteByte(':')
|
||||
itoa(buf, sec, 2)
|
||||
if l.flag&Lmicroseconds != 0 {
|
||||
buf.WriteByte('.')
|
||||
itoa(buf, t.Nanosecond()/1e3, 6)
|
||||
}
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
if reqId != "" {
|
||||
buf.WriteByte('[')
|
||||
buf.WriteString(reqId)
|
||||
buf.WriteByte(']')
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
|
||||
if l.flag&(Lshortcolor|Llongcolor) != 0 {
|
||||
buf.WriteString(fmt.Sprintf("\033[1;%dm", colors[lvl]))
|
||||
}
|
||||
if l.flag&Llevel != 0 {
|
||||
buf.WriteString(levels[lvl])
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
if l.flag&Lshortcolor != 0 {
|
||||
buf.WriteString("\033[0m")
|
||||
}
|
||||
|
||||
if l.flag&Lmodule != 0 {
|
||||
buf.WriteByte('[')
|
||||
buf.WriteString(moduleOf(file))
|
||||
buf.WriteByte(']')
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
if l.flag&(Lshortfile|Llongfile) != 0 {
|
||||
if l.flag&Lshortfile != 0 {
|
||||
short := file
|
||||
for i := len(file) - 1; i > 0; i-- {
|
||||
if file[i] == '/' {
|
||||
short = file[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
file = short
|
||||
}
|
||||
buf.WriteString(file)
|
||||
buf.WriteByte(':')
|
||||
itoa(buf, line, -1)
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
|
||||
// Output writes the output for a logging event. The string s contains
|
||||
// the text to print after the prefix specified by the flags of the
|
||||
// Logger. A newline is appended if the last character of s is not
|
||||
// already a newline. Calldepth is used to recover the PC and is
|
||||
// provided for generality, although at the moment on all pre-defined
|
||||
// paths it will be 2.
|
||||
func (l *Logger) Output(reqId string, lvl int, calldepth int, s string) error {
|
||||
if lvl < l.Level {
|
||||
return nil
|
||||
}
|
||||
now := time.Now().In(l.loc) // get this early.
|
||||
var file string
|
||||
var line int
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.flag&(Lshortfile|Llongfile|Lmodule) != 0 {
|
||||
// release lock while getting caller info - it's expensive.
|
||||
l.mu.Unlock()
|
||||
var ok bool
|
||||
_, file, line, ok = runtime.Caller(calldepth)
|
||||
if !ok {
|
||||
file = "???"
|
||||
line = 0
|
||||
}
|
||||
l.mu.Lock()
|
||||
}
|
||||
l.levelStats[lvl]++
|
||||
l.buf.Reset()
|
||||
l.formatHeader(&l.buf, now, file, line, lvl, reqId)
|
||||
l.buf.WriteString(s)
|
||||
if l.flag&Llongcolor != 0 {
|
||||
l.buf.WriteString("\033[0m")
|
||||
}
|
||||
if len(s) > 0 && s[len(s)-1] != '\n' {
|
||||
l.buf.WriteByte('\n')
|
||||
}
|
||||
_, err := l.out.Write(l.buf.Bytes())
|
||||
return err
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Printf calls l.Output to print to the logger.
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
||||
func (l *Logger) Printf(format string, v ...interface{}) {
|
||||
l.Output("", Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Print calls l.Output to print to the logger.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
func (l *Logger) Print(v ...interface{}) {
|
||||
l.Output("", Linfo, 2, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Println calls l.Output to print to the logger.
|
||||
// Arguments are handled in the manner of fmt.Println.
|
||||
func (l *Logger) Println(v ...interface{}) {
|
||||
l.Output("", Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
||||
l.Output("", Ldebug, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(v ...interface{}) {
|
||||
l.Output("", Ldebug, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
||||
l.Output("", Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *Logger) Info(v ...interface{}) {
|
||||
l.Output("", Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
func (l *Logger) Warnf(format string, v ...interface{}) {
|
||||
l.Output("", Lwarn, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(v ...interface{}) {
|
||||
l.Output("", Lwarn, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
||||
l.Output("", Lerror, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (l *Logger) Error(v ...interface{}) {
|
||||
l.Output("", Lerror, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (l *Logger) Fatal(v ...interface{}) {
|
||||
l.Output("", Lfatal, 2, fmt.Sprintln(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||
l.Output("", Lfatal, 2, fmt.Sprintf(format, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
// Panic is equivalent to l.Print() followed by a call to panic().
|
||||
func (l *Logger) Panic(v ...interface{}) {
|
||||
s := fmt.Sprintln(v...)
|
||||
l.Output("", Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// Panicf is equivalent to l.Printf() followed by a call to panic().
|
||||
func (l *Logger) Panicf(format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
l.Output("", Lpanic, 2, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
func (l *Logger) Stack(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
s += "\n"
|
||||
buf := make([]byte, 1024*1024)
|
||||
n := runtime.Stack(buf, true)
|
||||
s += string(buf[:n])
|
||||
s += "\n"
|
||||
l.Output("", Lerror, 2, s)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
func (l *Logger) Stat() (stats []int64) {
|
||||
l.mu.Lock()
|
||||
v := l.levelStats
|
||||
l.mu.Unlock()
|
||||
return v[:]
|
||||
}
|
||||
|
||||
// Flags returns the output flags for the logger.
|
||||
func (l *Logger) Flags() int {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.flag
|
||||
}
|
||||
|
||||
func RmColorFlags(flag int) int {
|
||||
// for un std out, it should not show color since almost them don't support
|
||||
if flag&Llongcolor != 0 {
|
||||
flag = flag ^ Llongcolor
|
||||
}
|
||||
if flag&Lshortcolor != 0 {
|
||||
flag = flag ^ Lshortcolor
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
||||
func (l *Logger) Location() *time.Location {
|
||||
return l.loc
|
||||
}
|
||||
|
||||
func (l *Logger) SetLocation(loc *time.Location) {
|
||||
l.loc = loc
|
||||
}
|
||||
|
||||
// SetFlags sets the output flags for the logger.
|
||||
func (l *Logger) SetFlags(flag int) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.out != os.Stdout {
|
||||
flag = RmColorFlags(flag)
|
||||
}
|
||||
l.flag = flag
|
||||
}
|
||||
|
||||
// Prefix returns the output prefix for the logger.
|
||||
func (l *Logger) Prefix() string {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.prefix
|
||||
}
|
||||
|
||||
// SetPrefix sets the output prefix for the logger.
|
||||
func (l *Logger) SetPrefix(prefix string) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.prefix = prefix
|
||||
}
|
||||
|
||||
// SetOutputLevel sets the output level for the logger.
|
||||
func (l *Logger) SetOutputLevel(lvl int) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.Level = lvl
|
||||
}
|
||||
|
||||
func (l *Logger) OutputLevel() int {
|
||||
return l.Level
|
||||
}
|
||||
|
||||
func (l *Logger) SetOutput(w io.Writer) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
l.out = w
|
||||
if w != os.Stdout {
|
||||
l.flag = RmColorFlags(l.flag)
|
||||
}
|
||||
}
|
||||
|
||||
// SetOutput sets the output destination for the standard logger.
|
||||
func SetOutput(w io.Writer) {
|
||||
Std.SetOutput(w)
|
||||
}
|
||||
|
||||
func SetLocation(loc *time.Location) {
|
||||
Std.SetLocation(loc)
|
||||
}
|
||||
|
||||
func Location() *time.Location {
|
||||
return Std.Location()
|
||||
}
|
||||
|
||||
// Flags returns the output flags for the standard logger.
|
||||
func Flags() int {
|
||||
return Std.Flags()
|
||||
}
|
||||
|
||||
// SetFlags sets the output flags for the standard logger.
|
||||
func SetFlags(flag int) {
|
||||
Std.SetFlags(flag)
|
||||
}
|
||||
|
||||
// Prefix returns the output prefix for the standard logger.
|
||||
func Prefix() string {
|
||||
return Std.Prefix()
|
||||
}
|
||||
|
||||
// SetPrefix sets the output prefix for the standard logger.
|
||||
func SetPrefix(prefix string) {
|
||||
Std.SetPrefix(prefix)
|
||||
}
|
||||
|
||||
func SetOutputLevel(lvl int) {
|
||||
Std.SetOutputLevel(lvl)
|
||||
}
|
||||
|
||||
func OutputLevel() int {
|
||||
return Std.OutputLevel()
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Print calls Output to print to the standard logger.
|
||||
// Arguments are handled in the manner of fmt.Print.
|
||||
func Print(v ...interface{}) {
|
||||
Std.Output("", Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// Printf calls Output to print to the standard logger.
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
||||
func Printf(format string, v ...interface{}) {
|
||||
Std.Output("", Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Println calls Output to print to the standard logger.
|
||||
// Arguments are handled in the manner of fmt.Println.
|
||||
func Println(v ...interface{}) {
|
||||
Std.Output("", Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
Std.Output("", Ldebug, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Debug(v ...interface{}) {
|
||||
Std.Output("", Ldebug, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Infof(format string, v ...interface{}) {
|
||||
Std.Output("", Linfo, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Info(v ...interface{}) {
|
||||
Std.Output("", Linfo, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Warnf(format string, v ...interface{}) {
|
||||
Std.Output("", Lwarn, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Warn(v ...interface{}) {
|
||||
Std.Output("", Lwarn, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
Std.Output("", Lerror, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Error(v ...interface{}) {
|
||||
Std.Output("", Lerror, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
|
||||
func Fatal(v ...interface{}) {
|
||||
Std.Output("", Lfatal, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// Fatalf is equivalent to Printf() followed by a call to os.Exit(1).
|
||||
func Fatalf(format string, v ...interface{}) {
|
||||
Std.Output("", Lfatal, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
// Panic is equivalent to Print() followed by a call to panic().
|
||||
func Panic(v ...interface{}) {
|
||||
Std.Output("", Lpanic, 2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// Panicf is equivalent to Printf() followed by a call to panic().
|
||||
func Panicf(format string, v ...interface{}) {
|
||||
Std.Output("", Lpanic, 2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func Stack(v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
s += "\n"
|
||||
buf := make([]byte, 1024*1024)
|
||||
n := runtime.Stack(buf, true)
|
||||
s += string(buf[:n])
|
||||
s += "\n"
|
||||
Std.Output("", Lerror, 2, s)
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
21
vendor/github.com/lunny/nodb/LICENSE
generated
vendored
Normal file
21
vendor/github.com/lunny/nodb/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 siddontang
|
||||
|
||||
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.
|
106
vendor/github.com/lunny/nodb/batch.go
generated
vendored
Normal file
106
vendor/github.com/lunny/nodb/batch.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
type batch struct {
|
||||
l *Nodb
|
||||
|
||||
store.WriteBatch
|
||||
|
||||
sync.Locker
|
||||
|
||||
logs [][]byte
|
||||
|
||||
tx *Tx
|
||||
}
|
||||
|
||||
func (b *batch) Commit() error {
|
||||
b.l.commitLock.Lock()
|
||||
defer b.l.commitLock.Unlock()
|
||||
|
||||
err := b.WriteBatch.Commit()
|
||||
|
||||
if b.l.binlog != nil {
|
||||
if err == nil {
|
||||
if b.tx == nil {
|
||||
b.l.binlog.Log(b.logs...)
|
||||
} else {
|
||||
b.tx.logs = append(b.tx.logs, b.logs...)
|
||||
}
|
||||
}
|
||||
b.logs = [][]byte{}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *batch) Lock() {
|
||||
b.Locker.Lock()
|
||||
}
|
||||
|
||||
func (b *batch) Unlock() {
|
||||
if b.l.binlog != nil {
|
||||
b.logs = [][]byte{}
|
||||
}
|
||||
b.WriteBatch.Rollback()
|
||||
b.Locker.Unlock()
|
||||
}
|
||||
|
||||
func (b *batch) Put(key []byte, value []byte) {
|
||||
if b.l.binlog != nil {
|
||||
buf := encodeBinLogPut(key, value)
|
||||
b.logs = append(b.logs, buf)
|
||||
}
|
||||
b.WriteBatch.Put(key, value)
|
||||
}
|
||||
|
||||
func (b *batch) Delete(key []byte) {
|
||||
if b.l.binlog != nil {
|
||||
buf := encodeBinLogDelete(key)
|
||||
b.logs = append(b.logs, buf)
|
||||
}
|
||||
b.WriteBatch.Delete(key)
|
||||
}
|
||||
|
||||
type dbBatchLocker struct {
|
||||
l *sync.Mutex
|
||||
wrLock *sync.RWMutex
|
||||
}
|
||||
|
||||
func (l *dbBatchLocker) Lock() {
|
||||
l.wrLock.RLock()
|
||||
l.l.Lock()
|
||||
}
|
||||
|
||||
func (l *dbBatchLocker) Unlock() {
|
||||
l.l.Unlock()
|
||||
l.wrLock.RUnlock()
|
||||
}
|
||||
|
||||
type txBatchLocker struct {
|
||||
}
|
||||
|
||||
func (l *txBatchLocker) Lock() {}
|
||||
func (l *txBatchLocker) Unlock() {}
|
||||
|
||||
type multiBatchLocker struct {
|
||||
}
|
||||
|
||||
func (l *multiBatchLocker) Lock() {}
|
||||
func (l *multiBatchLocker) Unlock() {}
|
||||
|
||||
func (l *Nodb) newBatch(wb store.WriteBatch, locker sync.Locker, tx *Tx) *batch {
|
||||
b := new(batch)
|
||||
b.l = l
|
||||
b.WriteBatch = wb
|
||||
|
||||
b.tx = tx
|
||||
b.Locker = locker
|
||||
|
||||
b.logs = [][]byte{}
|
||||
return b
|
||||
}
|
391
vendor/github.com/lunny/nodb/binlog.go
generated
vendored
Normal file
391
vendor/github.com/lunny/nodb/binlog.go
generated
vendored
Normal file
@ -0,0 +1,391 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/log"
|
||||
"github.com/lunny/nodb/config"
|
||||
)
|
||||
|
||||
type BinLogHead struct {
|
||||
CreateTime uint32
|
||||
BatchId uint32
|
||||
PayloadLen uint32
|
||||
}
|
||||
|
||||
func (h *BinLogHead) Len() int {
|
||||
return 12
|
||||
}
|
||||
|
||||
func (h *BinLogHead) Write(w io.Writer) error {
|
||||
if err := binary.Write(w, binary.BigEndian, h.CreateTime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Write(w, binary.BigEndian, h.BatchId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Write(w, binary.BigEndian, h.PayloadLen); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *BinLogHead) handleReadError(err error) error {
|
||||
if err == io.EOF {
|
||||
return io.ErrUnexpectedEOF
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (h *BinLogHead) Read(r io.Reader) error {
|
||||
var err error
|
||||
if err = binary.Read(r, binary.BigEndian, &h.CreateTime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = binary.Read(r, binary.BigEndian, &h.BatchId); err != nil {
|
||||
return h.handleReadError(err)
|
||||
}
|
||||
|
||||
if err = binary.Read(r, binary.BigEndian, &h.PayloadLen); err != nil {
|
||||
return h.handleReadError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *BinLogHead) InSameBatch(ho *BinLogHead) bool {
|
||||
if h.CreateTime == ho.CreateTime && h.BatchId == ho.BatchId {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
index file format:
|
||||
ledis-bin.00001
|
||||
ledis-bin.00002
|
||||
ledis-bin.00003
|
||||
|
||||
log file format
|
||||
|
||||
Log: Head|PayloadData
|
||||
|
||||
Head: createTime|batchId|payloadData
|
||||
|
||||
*/
|
||||
|
||||
type BinLog struct {
|
||||
sync.Mutex
|
||||
|
||||
path string
|
||||
|
||||
cfg *config.BinLogConfig
|
||||
|
||||
logFile *os.File
|
||||
|
||||
logWb *bufio.Writer
|
||||
|
||||
indexName string
|
||||
logNames []string
|
||||
lastLogIndex int64
|
||||
|
||||
batchId uint32
|
||||
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
func NewBinLog(cfg *config.Config) (*BinLog, error) {
|
||||
l := new(BinLog)
|
||||
|
||||
l.cfg = &cfg.BinLog
|
||||
l.cfg.Adjust()
|
||||
|
||||
l.path = path.Join(cfg.DataDir, "binlog")
|
||||
|
||||
if err := os.MkdirAll(l.path, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l.logNames = make([]string, 0, 16)
|
||||
|
||||
l.ch = make(chan struct{})
|
||||
|
||||
if err := l.loadIndex(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *BinLog) flushIndex() error {
|
||||
data := strings.Join(l.logNames, "\n")
|
||||
|
||||
bakName := fmt.Sprintf("%s.bak", l.indexName)
|
||||
f, err := os.OpenFile(bakName, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
log.Error("create binlog bak index error %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := f.WriteString(data); err != nil {
|
||||
log.Error("write binlog index error %s", err.Error())
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
f.Close()
|
||||
|
||||
if err := os.Rename(bakName, l.indexName); err != nil {
|
||||
log.Error("rename binlog bak index error %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *BinLog) loadIndex() error {
|
||||
l.indexName = path.Join(l.path, fmt.Sprintf("ledis-bin.index"))
|
||||
if _, err := os.Stat(l.indexName); os.IsNotExist(err) {
|
||||
//no index file, nothing to do
|
||||
} else {
|
||||
indexData, err := ioutil.ReadFile(l.indexName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(indexData), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.Trim(line, "\r\n ")
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path.Join(l.path, line)); err != nil {
|
||||
log.Error("load index line %s error %s", line, err.Error())
|
||||
return err
|
||||
} else {
|
||||
l.logNames = append(l.logNames, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
if l.cfg.MaxFileNum > 0 && len(l.logNames) > l.cfg.MaxFileNum {
|
||||
//remove oldest logfile
|
||||
if err := l.Purge(len(l.logNames) - l.cfg.MaxFileNum); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
if len(l.logNames) == 0 {
|
||||
l.lastLogIndex = 1
|
||||
} else {
|
||||
lastName := l.logNames[len(l.logNames)-1]
|
||||
|
||||
if l.lastLogIndex, err = strconv.ParseInt(path.Ext(lastName)[1:], 10, 64); err != nil {
|
||||
log.Error("invalid logfile name %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
//like mysql, if server restart, a new binlog will create
|
||||
l.lastLogIndex++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *BinLog) getLogFile() string {
|
||||
return l.FormatLogFileName(l.lastLogIndex)
|
||||
}
|
||||
|
||||
func (l *BinLog) openNewLogFile() error {
|
||||
var err error
|
||||
lastName := l.getLogFile()
|
||||
|
||||
logPath := path.Join(l.path, lastName)
|
||||
if l.logFile, err = os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY, 0666); err != nil {
|
||||
log.Error("open new logfile error %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if l.cfg.MaxFileNum > 0 && len(l.logNames) == l.cfg.MaxFileNum {
|
||||
l.purge(1)
|
||||
}
|
||||
|
||||
l.logNames = append(l.logNames, lastName)
|
||||
|
||||
if l.logWb == nil {
|
||||
l.logWb = bufio.NewWriterSize(l.logFile, 1024)
|
||||
} else {
|
||||
l.logWb.Reset(l.logFile)
|
||||
}
|
||||
|
||||
if err = l.flushIndex(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *BinLog) checkLogFileSize() bool {
|
||||
if l.logFile == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
st, _ := l.logFile.Stat()
|
||||
if st.Size() >= int64(l.cfg.MaxFileSize) {
|
||||
l.closeLog()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *BinLog) closeLog() {
|
||||
l.lastLogIndex++
|
||||
|
||||
l.logFile.Close()
|
||||
l.logFile = nil
|
||||
}
|
||||
|
||||
func (l *BinLog) purge(n int) {
|
||||
for i := 0; i < n; i++ {
|
||||
logPath := path.Join(l.path, l.logNames[i])
|
||||
os.Remove(logPath)
|
||||
}
|
||||
|
||||
copy(l.logNames[0:], l.logNames[n:])
|
||||
l.logNames = l.logNames[0 : len(l.logNames)-n]
|
||||
}
|
||||
|
||||
func (l *BinLog) Close() {
|
||||
if l.logFile != nil {
|
||||
l.logFile.Close()
|
||||
l.logFile = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *BinLog) LogNames() []string {
|
||||
return l.logNames
|
||||
}
|
||||
|
||||
func (l *BinLog) LogFileName() string {
|
||||
return l.getLogFile()
|
||||
}
|
||||
|
||||
func (l *BinLog) LogFilePos() int64 {
|
||||
if l.logFile == nil {
|
||||
return 0
|
||||
} else {
|
||||
st, _ := l.logFile.Stat()
|
||||
return st.Size()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *BinLog) LogFileIndex() int64 {
|
||||
return l.lastLogIndex
|
||||
}
|
||||
|
||||
func (l *BinLog) FormatLogFileName(index int64) string {
|
||||
return fmt.Sprintf("ledis-bin.%07d", index)
|
||||
}
|
||||
|
||||
func (l *BinLog) FormatLogFilePath(index int64) string {
|
||||
return path.Join(l.path, l.FormatLogFileName(index))
|
||||
}
|
||||
|
||||
func (l *BinLog) LogPath() string {
|
||||
return l.path
|
||||
}
|
||||
|
||||
func (l *BinLog) Purge(n int) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if len(l.logNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if n >= len(l.logNames) {
|
||||
n = len(l.logNames)
|
||||
//can not purge current log file
|
||||
if l.logNames[n-1] == l.getLogFile() {
|
||||
n = n - 1
|
||||
}
|
||||
}
|
||||
|
||||
l.purge(n)
|
||||
|
||||
return l.flushIndex()
|
||||
}
|
||||
|
||||
func (l *BinLog) PurgeAll() error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.closeLog()
|
||||
return l.openNewLogFile()
|
||||
}
|
||||
|
||||
func (l *BinLog) Log(args ...[]byte) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
var err error
|
||||
|
||||
if l.logFile == nil {
|
||||
if err = l.openNewLogFile(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
head := &BinLogHead{}
|
||||
|
||||
head.CreateTime = uint32(time.Now().Unix())
|
||||
head.BatchId = l.batchId
|
||||
|
||||
l.batchId++
|
||||
|
||||
for _, data := range args {
|
||||
head.PayloadLen = uint32(len(data))
|
||||
|
||||
if err := head.Write(l.logWb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := l.logWb.Write(data); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = l.logWb.Flush(); err != nil {
|
||||
log.Error("write log error %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
l.checkLogFileSize()
|
||||
|
||||
close(l.ch)
|
||||
l.ch = make(chan struct{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *BinLog) Wait() <-chan struct{} {
|
||||
return l.ch
|
||||
}
|
215
vendor/github.com/lunny/nodb/binlog_util.go
generated
vendored
Normal file
215
vendor/github.com/lunny/nodb/binlog_util.go
generated
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
errBinLogDeleteType = errors.New("invalid bin log delete type")
|
||||
errBinLogPutType = errors.New("invalid bin log put type")
|
||||
errBinLogCommandType = errors.New("invalid bin log command type")
|
||||
)
|
||||
|
||||
func encodeBinLogDelete(key []byte) []byte {
|
||||
buf := make([]byte, 1+len(key))
|
||||
buf[0] = BinLogTypeDeletion
|
||||
copy(buf[1:], key)
|
||||
return buf
|
||||
}
|
||||
|
||||
func decodeBinLogDelete(sz []byte) ([]byte, error) {
|
||||
if len(sz) < 1 || sz[0] != BinLogTypeDeletion {
|
||||
return nil, errBinLogDeleteType
|
||||
}
|
||||
|
||||
return sz[1:], nil
|
||||
}
|
||||
|
||||
func encodeBinLogPut(key []byte, value []byte) []byte {
|
||||
buf := make([]byte, 3+len(key)+len(value))
|
||||
buf[0] = BinLogTypePut
|
||||
pos := 1
|
||||
binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
|
||||
pos += 2
|
||||
copy(buf[pos:], key)
|
||||
pos += len(key)
|
||||
copy(buf[pos:], value)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func decodeBinLogPut(sz []byte) ([]byte, []byte, error) {
|
||||
if len(sz) < 3 || sz[0] != BinLogTypePut {
|
||||
return nil, nil, errBinLogPutType
|
||||
}
|
||||
|
||||
keyLen := int(binary.BigEndian.Uint16(sz[1:]))
|
||||
if 3+keyLen > len(sz) {
|
||||
return nil, nil, errBinLogPutType
|
||||
}
|
||||
|
||||
return sz[3 : 3+keyLen], sz[3+keyLen:], nil
|
||||
}
|
||||
|
||||
func FormatBinLogEvent(event []byte) (string, error) {
|
||||
logType := uint8(event[0])
|
||||
|
||||
var err error
|
||||
var k []byte
|
||||
var v []byte
|
||||
|
||||
var buf []byte = make([]byte, 0, 1024)
|
||||
|
||||
switch logType {
|
||||
case BinLogTypePut:
|
||||
k, v, err = decodeBinLogPut(event)
|
||||
buf = append(buf, "PUT "...)
|
||||
case BinLogTypeDeletion:
|
||||
k, err = decodeBinLogDelete(event)
|
||||
buf = append(buf, "DELETE "...)
|
||||
default:
|
||||
err = errInvalidBinLogEvent
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if buf, err = formatDataKey(buf, k); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if v != nil && len(v) != 0 {
|
||||
buf = append(buf, fmt.Sprintf(" %q", v)...)
|
||||
}
|
||||
|
||||
return String(buf), nil
|
||||
}
|
||||
|
||||
func formatDataKey(buf []byte, k []byte) ([]byte, error) {
|
||||
if len(k) < 2 {
|
||||
return nil, errInvalidBinLogEvent
|
||||
}
|
||||
|
||||
buf = append(buf, fmt.Sprintf("DB:%2d ", k[0])...)
|
||||
buf = append(buf, fmt.Sprintf("%s ", TypeName[k[1]])...)
|
||||
|
||||
db := new(DB)
|
||||
db.index = k[0]
|
||||
|
||||
//to do format at respective place
|
||||
|
||||
switch k[1] {
|
||||
case KVType:
|
||||
if key, err := db.decodeKVKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
}
|
||||
case HashType:
|
||||
if key, field, err := db.hDecodeHashKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendQuote(buf, String(field))
|
||||
}
|
||||
case HSizeType:
|
||||
if key, err := db.hDecodeSizeKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
}
|
||||
case ListType:
|
||||
if key, seq, err := db.lDecodeListKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendInt(buf, int64(seq), 10)
|
||||
}
|
||||
case LMetaType:
|
||||
if key, err := db.lDecodeMetaKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
}
|
||||
case ZSetType:
|
||||
if key, m, err := db.zDecodeSetKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendQuote(buf, String(m))
|
||||
}
|
||||
case ZSizeType:
|
||||
if key, err := db.zDecodeSizeKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
}
|
||||
case ZScoreType:
|
||||
if key, m, score, err := db.zDecodeScoreKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendQuote(buf, String(m))
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendInt(buf, score, 10)
|
||||
}
|
||||
case BitType:
|
||||
if key, seq, err := db.bDecodeBinKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendUint(buf, uint64(seq), 10)
|
||||
}
|
||||
case BitMetaType:
|
||||
if key, err := db.bDecodeMetaKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
}
|
||||
case SetType:
|
||||
if key, member, err := db.sDecodeSetKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendQuote(buf, String(member))
|
||||
}
|
||||
case SSizeType:
|
||||
if key, err := db.sDecodeSizeKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
}
|
||||
case ExpTimeType:
|
||||
if tp, key, t, err := db.expDecodeTimeKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = append(buf, TypeName[tp]...)
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendInt(buf, t, 10)
|
||||
}
|
||||
case ExpMetaType:
|
||||
if tp, key, err := db.expDecodeMetaKey(k); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
buf = append(buf, TypeName[tp]...)
|
||||
buf = append(buf, ' ')
|
||||
buf = strconv.AppendQuote(buf, String(key))
|
||||
}
|
||||
default:
|
||||
return nil, errInvalidBinLogEvent
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
135
vendor/github.com/lunny/nodb/config/config.go
generated
vendored
Normal file
135
vendor/github.com/lunny/nodb/config/config.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type Size int
|
||||
|
||||
const (
|
||||
DefaultAddr string = "127.0.0.1:6380"
|
||||
DefaultHttpAddr string = "127.0.0.1:11181"
|
||||
|
||||
DefaultDBName string = "goleveldb"
|
||||
|
||||
DefaultDataDir string = "./data"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxBinLogFileSize int = 1024 * 1024 * 1024
|
||||
MaxBinLogFileNum int = 10000
|
||||
|
||||
DefaultBinLogFileSize int = MaxBinLogFileSize
|
||||
DefaultBinLogFileNum int = 10
|
||||
)
|
||||
|
||||
type LevelDBConfig struct {
|
||||
Compression bool `toml:"compression"`
|
||||
BlockSize int `toml:"block_size"`
|
||||
WriteBufferSize int `toml:"write_buffer_size"`
|
||||
CacheSize int `toml:"cache_size"`
|
||||
MaxOpenFiles int `toml:"max_open_files"`
|
||||
}
|
||||
|
||||
type LMDBConfig struct {
|
||||
MapSize int `toml:"map_size"`
|
||||
NoSync bool `toml:"nosync"`
|
||||
}
|
||||
|
||||
type BinLogConfig struct {
|
||||
MaxFileSize int `toml:"max_file_size"`
|
||||
MaxFileNum int `toml:"max_file_num"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
DataDir string `toml:"data_dir"`
|
||||
|
||||
DBName string `toml:"db_name"`
|
||||
|
||||
LevelDB LevelDBConfig `toml:"leveldb"`
|
||||
|
||||
LMDB LMDBConfig `toml:"lmdb"`
|
||||
|
||||
BinLog BinLogConfig `toml:"binlog"`
|
||||
|
||||
SlaveOf string `toml:"slaveof"`
|
||||
|
||||
AccessLog string `toml:"access_log"`
|
||||
}
|
||||
|
||||
func NewConfigWithFile(fileName string) (*Config, error) {
|
||||
data, err := ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConfigWithData(data)
|
||||
}
|
||||
|
||||
func NewConfigWithData(data []byte) (*Config, error) {
|
||||
cfg := NewConfigDefault()
|
||||
|
||||
_, err := toml.Decode(string(data), cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func NewConfigDefault() *Config {
|
||||
cfg := new(Config)
|
||||
|
||||
cfg.DataDir = DefaultDataDir
|
||||
|
||||
cfg.DBName = DefaultDBName
|
||||
|
||||
// disable binlog
|
||||
cfg.BinLog.MaxFileNum = 0
|
||||
cfg.BinLog.MaxFileSize = 0
|
||||
|
||||
// disable replication
|
||||
cfg.SlaveOf = ""
|
||||
|
||||
// disable access log
|
||||
cfg.AccessLog = ""
|
||||
|
||||
cfg.LMDB.MapSize = 20 * 1024 * 1024
|
||||
cfg.LMDB.NoSync = true
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (cfg *LevelDBConfig) Adjust() {
|
||||
if cfg.CacheSize <= 0 {
|
||||
cfg.CacheSize = 4 * 1024 * 1024
|
||||
}
|
||||
|
||||
if cfg.BlockSize <= 0 {
|
||||
cfg.BlockSize = 4 * 1024
|
||||
}
|
||||
|
||||
if cfg.WriteBufferSize <= 0 {
|
||||
cfg.WriteBufferSize = 4 * 1024 * 1024
|
||||
}
|
||||
|
||||
if cfg.MaxOpenFiles < 1024 {
|
||||
cfg.MaxOpenFiles = 1024
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *BinLogConfig) Adjust() {
|
||||
if cfg.MaxFileSize <= 0 {
|
||||
cfg.MaxFileSize = DefaultBinLogFileSize
|
||||
} else if cfg.MaxFileSize > MaxBinLogFileSize {
|
||||
cfg.MaxFileSize = MaxBinLogFileSize
|
||||
}
|
||||
|
||||
if cfg.MaxFileNum <= 0 {
|
||||
cfg.MaxFileNum = DefaultBinLogFileNum
|
||||
} else if cfg.MaxFileNum > MaxBinLogFileNum {
|
||||
cfg.MaxFileNum = MaxBinLogFileNum
|
||||
}
|
||||
}
|
98
vendor/github.com/lunny/nodb/const.go
generated
vendored
Normal file
98
vendor/github.com/lunny/nodb/const.go
generated
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
const (
|
||||
NoneType byte = 0
|
||||
KVType byte = 1
|
||||
HashType byte = 2
|
||||
HSizeType byte = 3
|
||||
ListType byte = 4
|
||||
LMetaType byte = 5
|
||||
ZSetType byte = 6
|
||||
ZSizeType byte = 7
|
||||
ZScoreType byte = 8
|
||||
BitType byte = 9
|
||||
BitMetaType byte = 10
|
||||
SetType byte = 11
|
||||
SSizeType byte = 12
|
||||
|
||||
maxDataType byte = 100
|
||||
|
||||
ExpTimeType byte = 101
|
||||
ExpMetaType byte = 102
|
||||
)
|
||||
|
||||
var (
|
||||
TypeName = map[byte]string{
|
||||
KVType: "kv",
|
||||
HashType: "hash",
|
||||
HSizeType: "hsize",
|
||||
ListType: "list",
|
||||
LMetaType: "lmeta",
|
||||
ZSetType: "zset",
|
||||
ZSizeType: "zsize",
|
||||
ZScoreType: "zscore",
|
||||
BitType: "bit",
|
||||
BitMetaType: "bitmeta",
|
||||
SetType: "set",
|
||||
SSizeType: "ssize",
|
||||
ExpTimeType: "exptime",
|
||||
ExpMetaType: "expmeta",
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
defaultScanCount int = 10
|
||||
)
|
||||
|
||||
var (
|
||||
errKeySize = errors.New("invalid key size")
|
||||
errValueSize = errors.New("invalid value size")
|
||||
errHashFieldSize = errors.New("invalid hash field size")
|
||||
errSetMemberSize = errors.New("invalid set member size")
|
||||
errZSetMemberSize = errors.New("invalid zset member size")
|
||||
errExpireValue = errors.New("invalid expire value")
|
||||
)
|
||||
|
||||
const (
|
||||
//we don't support too many databases
|
||||
MaxDBNumber uint8 = 16
|
||||
|
||||
//max key size
|
||||
MaxKeySize int = 1024
|
||||
|
||||
//max hash field size
|
||||
MaxHashFieldSize int = 1024
|
||||
|
||||
//max zset member size
|
||||
MaxZSetMemberSize int = 1024
|
||||
|
||||
//max set member size
|
||||
MaxSetMemberSize int = 1024
|
||||
|
||||
//max value size
|
||||
MaxValueSize int = 10 * 1024 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
ErrScoreMiss = errors.New("zset score miss")
|
||||
)
|
||||
|
||||
const (
|
||||
BinLogTypeDeletion uint8 = 0x0
|
||||
BinLogTypePut uint8 = 0x1
|
||||
BinLogTypeCommand uint8 = 0x2
|
||||
)
|
||||
|
||||
const (
|
||||
DBAutoCommit uint8 = 0x0
|
||||
DBInTransaction uint8 = 0x1
|
||||
DBInMulti uint8 = 0x2
|
||||
)
|
||||
|
||||
var (
|
||||
Version = "0.1"
|
||||
)
|
61
vendor/github.com/lunny/nodb/doc.go
generated
vendored
Normal file
61
vendor/github.com/lunny/nodb/doc.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
// package nodb is a high performance embedded NoSQL.
|
||||
//
|
||||
// nodb supports various data structure like kv, list, hash and zset like redis.
|
||||
//
|
||||
// Other features include binlog replication, data with a limited time-to-live.
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// First create a nodb instance before use:
|
||||
//
|
||||
// l := nodb.Open(cfg)
|
||||
//
|
||||
// cfg is a Config instance which contains configuration for nodb use,
|
||||
// like DataDir (root directory for nodb working to store data).
|
||||
//
|
||||
// After you create a nodb instance, you can select a DB to store you data:
|
||||
//
|
||||
// db, _ := l.Select(0)
|
||||
//
|
||||
// DB must be selected by a index, nodb supports only 16 databases, so the index range is [0-15].
|
||||
//
|
||||
// KV
|
||||
//
|
||||
// KV is the most basic nodb type like any other key-value database.
|
||||
//
|
||||
// err := db.Set(key, value)
|
||||
// value, err := db.Get(key)
|
||||
//
|
||||
// List
|
||||
//
|
||||
// List is simply lists of values, sorted by insertion order.
|
||||
// You can push or pop value on the list head (left) or tail (right).
|
||||
//
|
||||
// err := db.LPush(key, value1)
|
||||
// err := db.RPush(key, value2)
|
||||
// value1, err := db.LPop(key)
|
||||
// value2, err := db.RPop(key)
|
||||
//
|
||||
// Hash
|
||||
//
|
||||
// Hash is a map between fields and values.
|
||||
//
|
||||
// n, err := db.HSet(key, field1, value1)
|
||||
// n, err := db.HSet(key, field2, value2)
|
||||
// value1, err := db.HGet(key, field1)
|
||||
// value2, err := db.HGet(key, field2)
|
||||
//
|
||||
// ZSet
|
||||
//
|
||||
// ZSet is a sorted collections of values.
|
||||
// Every member of zset is associated with score, a int64 value which used to sort, from smallest to greatest score.
|
||||
// Members are unique, but score may be same.
|
||||
//
|
||||
// n, err := db.ZAdd(key, ScorePair{score1, member1}, ScorePair{score2, member2})
|
||||
// ay, err := db.ZRangeByScore(key, minScore, maxScore, 0, -1)
|
||||
//
|
||||
// Binlog
|
||||
//
|
||||
// nodb supports binlog, so you can sync binlog to another server for replication. If you want to open binlog support, set UseBinLog to true in config.
|
||||
//
|
||||
package nodb
|
200
vendor/github.com/lunny/nodb/dump.go
generated
vendored
Normal file
200
vendor/github.com/lunny/nodb/dump.go
generated
vendored
Normal file
@ -0,0 +1,200 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/siddontang/go-snappy/snappy"
|
||||
)
|
||||
|
||||
//dump format
|
||||
// fileIndex(bigendian int64)|filePos(bigendian int64)
|
||||
// |keylen(bigendian int32)|key|valuelen(bigendian int32)|value......
|
||||
//
|
||||
//key and value are both compressed for fast transfer dump on network using snappy
|
||||
|
||||
type BinLogAnchor struct {
|
||||
LogFileIndex int64
|
||||
LogPos int64
|
||||
}
|
||||
|
||||
func (m *BinLogAnchor) WriteTo(w io.Writer) error {
|
||||
if err := binary.Write(w, binary.BigEndian, m.LogFileIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Write(w, binary.BigEndian, m.LogPos); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BinLogAnchor) ReadFrom(r io.Reader) error {
|
||||
err := binary.Read(r, binary.BigEndian, &m.LogFileIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = binary.Read(r, binary.BigEndian, &m.LogPos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Nodb) DumpFile(path string) error {
|
||||
f, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return l.Dump(f)
|
||||
}
|
||||
|
||||
func (l *Nodb) Dump(w io.Writer) error {
|
||||
m := new(BinLogAnchor)
|
||||
|
||||
var err error
|
||||
|
||||
l.wLock.Lock()
|
||||
defer l.wLock.Unlock()
|
||||
|
||||
if l.binlog != nil {
|
||||
m.LogFileIndex = l.binlog.LogFileIndex()
|
||||
m.LogPos = l.binlog.LogFilePos()
|
||||
}
|
||||
|
||||
wb := bufio.NewWriterSize(w, 4096)
|
||||
if err = m.WriteTo(wb); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
it := l.ldb.NewIterator()
|
||||
it.SeekToFirst()
|
||||
|
||||
compressBuf := make([]byte, 4096)
|
||||
|
||||
var key []byte
|
||||
var value []byte
|
||||
for ; it.Valid(); it.Next() {
|
||||
key = it.RawKey()
|
||||
value = it.RawValue()
|
||||
|
||||
if key, err = snappy.Encode(compressBuf, key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = binary.Write(wb, binary.BigEndian, uint16(len(key))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = wb.Write(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if value, err = snappy.Encode(compressBuf, value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = binary.Write(wb, binary.BigEndian, uint32(len(value))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = wb.Write(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = wb.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
compressBuf = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Nodb) LoadDumpFile(path string) (*BinLogAnchor, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return l.LoadDump(f)
|
||||
}
|
||||
|
||||
func (l *Nodb) LoadDump(r io.Reader) (*BinLogAnchor, error) {
|
||||
l.wLock.Lock()
|
||||
defer l.wLock.Unlock()
|
||||
|
||||
info := new(BinLogAnchor)
|
||||
|
||||
rb := bufio.NewReaderSize(r, 4096)
|
||||
|
||||
err := info.ReadFrom(rb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var keyLen uint16
|
||||
var valueLen uint32
|
||||
|
||||
var keyBuf bytes.Buffer
|
||||
var valueBuf bytes.Buffer
|
||||
|
||||
deKeyBuf := make([]byte, 4096)
|
||||
deValueBuf := make([]byte, 4096)
|
||||
|
||||
var key, value []byte
|
||||
|
||||
for {
|
||||
if err = binary.Read(rb, binary.BigEndian, &keyLen); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
} else if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if _, err = io.CopyN(&keyBuf, rb, int64(keyLen)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if key, err = snappy.Decode(deKeyBuf, keyBuf.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = binary.Read(rb, binary.BigEndian, &valueLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err = io.CopyN(&valueBuf, rb, int64(valueLen)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if value, err = snappy.Decode(deValueBuf, valueBuf.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = l.ldb.Put(key, value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyBuf.Reset()
|
||||
valueBuf.Reset()
|
||||
}
|
||||
|
||||
deKeyBuf = nil
|
||||
deValueBuf = nil
|
||||
|
||||
//if binlog enable, we will delete all binlogs and open a new one for handling simply
|
||||
if l.binlog != nil {
|
||||
l.binlog.PurgeAll()
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
24
vendor/github.com/lunny/nodb/info.go
generated
vendored
Normal file
24
vendor/github.com/lunny/nodb/info.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package nodb
|
||||
|
||||
// todo, add info
|
||||
|
||||
// type Keyspace struct {
|
||||
// Kvs int `json:"kvs"`
|
||||
// KvExpires int `json:"kv_expires"`
|
||||
|
||||
// Lists int `json:"lists"`
|
||||
// ListExpires int `json:"list_expires"`
|
||||
|
||||
// Bitmaps int `json:"bitmaps"`
|
||||
// BitmapExpires int `json:"bitmap_expires"`
|
||||
|
||||
// ZSets int `json:"zsets"`
|
||||
// ZSetExpires int `json:"zset_expires"`
|
||||
|
||||
// Hashes int `json:"hashes"`
|
||||
// HashExpires int `json:"hahsh_expires"`
|
||||
// }
|
||||
|
||||
// type Info struct {
|
||||
// KeySpaces [MaxDBNumber]Keyspace
|
||||
// }
|
73
vendor/github.com/lunny/nodb/multi.go
generated
vendored
Normal file
73
vendor/github.com/lunny/nodb/multi.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNestMulti = errors.New("nest multi not supported")
|
||||
ErrMultiDone = errors.New("multi has been closed")
|
||||
)
|
||||
|
||||
type Multi struct {
|
||||
*DB
|
||||
}
|
||||
|
||||
func (db *DB) IsInMulti() bool {
|
||||
return db.status == DBInMulti
|
||||
}
|
||||
|
||||
// begin a mutli to execute commands,
|
||||
// it will block any other write operations before you close the multi, unlike transaction, mutli can not rollback
|
||||
func (db *DB) Multi() (*Multi, error) {
|
||||
if db.IsInMulti() {
|
||||
return nil, ErrNestMulti
|
||||
}
|
||||
|
||||
m := new(Multi)
|
||||
|
||||
m.DB = new(DB)
|
||||
m.DB.status = DBInMulti
|
||||
|
||||
m.DB.l = db.l
|
||||
|
||||
m.l.wLock.Lock()
|
||||
|
||||
m.DB.sdb = db.sdb
|
||||
|
||||
m.DB.bucket = db.sdb
|
||||
|
||||
m.DB.index = db.index
|
||||
|
||||
m.DB.kvBatch = m.newBatch()
|
||||
m.DB.listBatch = m.newBatch()
|
||||
m.DB.hashBatch = m.newBatch()
|
||||
m.DB.zsetBatch = m.newBatch()
|
||||
m.DB.binBatch = m.newBatch()
|
||||
m.DB.setBatch = m.newBatch()
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Multi) newBatch() *batch {
|
||||
return m.l.newBatch(m.bucket.NewWriteBatch(), &multiBatchLocker{}, nil)
|
||||
}
|
||||
|
||||
func (m *Multi) Close() error {
|
||||
if m.bucket == nil {
|
||||
return ErrMultiDone
|
||||
}
|
||||
m.l.wLock.Unlock()
|
||||
m.bucket = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Multi) Select(index int) error {
|
||||
if index < 0 || index >= int(MaxDBNumber) {
|
||||
return fmt.Errorf("invalid db index %d", index)
|
||||
}
|
||||
|
||||
m.DB.index = uint8(index)
|
||||
return nil
|
||||
}
|
128
vendor/github.com/lunny/nodb/nodb.go
generated
vendored
Normal file
128
vendor/github.com/lunny/nodb/nodb.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/log"
|
||||
"github.com/lunny/nodb/config"
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
type Nodb struct {
|
||||
cfg *config.Config
|
||||
|
||||
ldb *store.DB
|
||||
dbs [MaxDBNumber]*DB
|
||||
|
||||
quit chan struct{}
|
||||
jobs *sync.WaitGroup
|
||||
|
||||
binlog *BinLog
|
||||
|
||||
wLock sync.RWMutex //allow one write at same time
|
||||
commitLock sync.Mutex //allow one write commit at same time
|
||||
}
|
||||
|
||||
func Open(cfg *config.Config) (*Nodb, error) {
|
||||
if len(cfg.DataDir) == 0 {
|
||||
cfg.DataDir = config.DefaultDataDir
|
||||
}
|
||||
|
||||
ldb, err := store.Open(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := new(Nodb)
|
||||
|
||||
l.quit = make(chan struct{})
|
||||
l.jobs = new(sync.WaitGroup)
|
||||
|
||||
l.ldb = ldb
|
||||
|
||||
if cfg.BinLog.MaxFileNum > 0 && cfg.BinLog.MaxFileSize > 0 {
|
||||
l.binlog, err = NewBinLog(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
l.binlog = nil
|
||||
}
|
||||
|
||||
for i := uint8(0); i < MaxDBNumber; i++ {
|
||||
l.dbs[i] = l.newDB(i)
|
||||
}
|
||||
|
||||
l.activeExpireCycle()
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *Nodb) Close() {
|
||||
close(l.quit)
|
||||
l.jobs.Wait()
|
||||
|
||||
l.ldb.Close()
|
||||
|
||||
if l.binlog != nil {
|
||||
l.binlog.Close()
|
||||
l.binlog = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Nodb) Select(index int) (*DB, error) {
|
||||
if index < 0 || index >= int(MaxDBNumber) {
|
||||
return nil, fmt.Errorf("invalid db index %d", index)
|
||||
}
|
||||
|
||||
return l.dbs[index], nil
|
||||
}
|
||||
|
||||
func (l *Nodb) FlushAll() error {
|
||||
for index, db := range l.dbs {
|
||||
if _, err := db.FlushAll(); err != nil {
|
||||
log.Error("flush db %d error %s", index, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// very dangerous to use
|
||||
func (l *Nodb) DataDB() *store.DB {
|
||||
return l.ldb
|
||||
}
|
||||
|
||||
func (l *Nodb) activeExpireCycle() {
|
||||
var executors []*elimination = make([]*elimination, len(l.dbs))
|
||||
for i, db := range l.dbs {
|
||||
executors[i] = db.newEliminator()
|
||||
}
|
||||
|
||||
l.jobs.Add(1)
|
||||
go func() {
|
||||
tick := time.NewTicker(1 * time.Second)
|
||||
end := false
|
||||
done := make(chan struct{})
|
||||
for !end {
|
||||
select {
|
||||
case <-tick.C:
|
||||
go func() {
|
||||
for _, eli := range executors {
|
||||
eli.active()
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
<-done
|
||||
case <-l.quit:
|
||||
end = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tick.Stop()
|
||||
l.jobs.Done()
|
||||
}()
|
||||
}
|
171
vendor/github.com/lunny/nodb/nodb_db.go
generated
vendored
Normal file
171
vendor/github.com/lunny/nodb/nodb_db.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
type ibucket interface {
|
||||
Get(key []byte) ([]byte, error)
|
||||
|
||||
Put(key []byte, value []byte) error
|
||||
Delete(key []byte) error
|
||||
|
||||
NewIterator() *store.Iterator
|
||||
|
||||
NewWriteBatch() store.WriteBatch
|
||||
|
||||
RangeIterator(min []byte, max []byte, rangeType uint8) *store.RangeLimitIterator
|
||||
RevRangeIterator(min []byte, max []byte, rangeType uint8) *store.RangeLimitIterator
|
||||
RangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *store.RangeLimitIterator
|
||||
RevRangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *store.RangeLimitIterator
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
l *Nodb
|
||||
|
||||
sdb *store.DB
|
||||
|
||||
bucket ibucket
|
||||
|
||||
index uint8
|
||||
|
||||
kvBatch *batch
|
||||
listBatch *batch
|
||||
hashBatch *batch
|
||||
zsetBatch *batch
|
||||
binBatch *batch
|
||||
setBatch *batch
|
||||
|
||||
status uint8
|
||||
}
|
||||
|
||||
func (l *Nodb) newDB(index uint8) *DB {
|
||||
d := new(DB)
|
||||
|
||||
d.l = l
|
||||
|
||||
d.sdb = l.ldb
|
||||
|
||||
d.bucket = d.sdb
|
||||
|
||||
d.status = DBAutoCommit
|
||||
d.index = index
|
||||
|
||||
d.kvBatch = d.newBatch()
|
||||
d.listBatch = d.newBatch()
|
||||
d.hashBatch = d.newBatch()
|
||||
d.zsetBatch = d.newBatch()
|
||||
d.binBatch = d.newBatch()
|
||||
d.setBatch = d.newBatch()
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (db *DB) newBatch() *batch {
|
||||
return db.l.newBatch(db.bucket.NewWriteBatch(), &dbBatchLocker{l: &sync.Mutex{}, wrLock: &db.l.wLock}, nil)
|
||||
}
|
||||
|
||||
func (db *DB) Index() int {
|
||||
return int(db.index)
|
||||
}
|
||||
|
||||
func (db *DB) IsAutoCommit() bool {
|
||||
return db.status == DBAutoCommit
|
||||
}
|
||||
|
||||
func (db *DB) FlushAll() (drop int64, err error) {
|
||||
all := [...](func() (int64, error)){
|
||||
db.flush,
|
||||
db.lFlush,
|
||||
db.hFlush,
|
||||
db.zFlush,
|
||||
db.bFlush,
|
||||
db.sFlush}
|
||||
|
||||
for _, flush := range all {
|
||||
if n, e := flush(); e != nil {
|
||||
err = e
|
||||
return
|
||||
} else {
|
||||
drop += n
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) newEliminator() *elimination {
|
||||
eliminator := newEliminator(db)
|
||||
|
||||
eliminator.regRetireContext(KVType, db.kvBatch, db.delete)
|
||||
eliminator.regRetireContext(ListType, db.listBatch, db.lDelete)
|
||||
eliminator.regRetireContext(HashType, db.hashBatch, db.hDelete)
|
||||
eliminator.regRetireContext(ZSetType, db.zsetBatch, db.zDelete)
|
||||
eliminator.regRetireContext(BitType, db.binBatch, db.bDelete)
|
||||
eliminator.regRetireContext(SetType, db.setBatch, db.sDelete)
|
||||
|
||||
return eliminator
|
||||
}
|
||||
|
||||
func (db *DB) flushRegion(t *batch, minKey []byte, maxKey []byte) (drop int64, err error) {
|
||||
it := db.bucket.RangeIterator(minKey, maxKey, store.RangeROpen)
|
||||
for ; it.Valid(); it.Next() {
|
||||
t.Delete(it.RawKey())
|
||||
drop++
|
||||
if drop&1023 == 0 {
|
||||
if err = t.Commit(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
it.Close()
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) flushType(t *batch, dataType byte) (drop int64, err error) {
|
||||
var deleteFunc func(t *batch, key []byte) int64
|
||||
var metaDataType byte
|
||||
switch dataType {
|
||||
case KVType:
|
||||
deleteFunc = db.delete
|
||||
metaDataType = KVType
|
||||
case ListType:
|
||||
deleteFunc = db.lDelete
|
||||
metaDataType = LMetaType
|
||||
case HashType:
|
||||
deleteFunc = db.hDelete
|
||||
metaDataType = HSizeType
|
||||
case ZSetType:
|
||||
deleteFunc = db.zDelete
|
||||
metaDataType = ZSizeType
|
||||
case BitType:
|
||||
deleteFunc = db.bDelete
|
||||
metaDataType = BitMetaType
|
||||
case SetType:
|
||||
deleteFunc = db.sDelete
|
||||
metaDataType = SSizeType
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid data type: %s", TypeName[dataType])
|
||||
}
|
||||
|
||||
var keys [][]byte
|
||||
keys, err = db.scan(metaDataType, nil, 1024, false, "")
|
||||
for len(keys) != 0 || err != nil {
|
||||
for _, key := range keys {
|
||||
deleteFunc(t, key)
|
||||
db.rmExpire(t, dataType, key)
|
||||
|
||||
}
|
||||
|
||||
if err = t.Commit(); err != nil {
|
||||
return
|
||||
} else {
|
||||
drop += int64(len(keys))
|
||||
}
|
||||
keys, err = db.scan(metaDataType, nil, 1024, false, "")
|
||||
}
|
||||
return
|
||||
}
|
312
vendor/github.com/lunny/nodb/replication.go
generated
vendored
Normal file
312
vendor/github.com/lunny/nodb/replication.go
generated
vendored
Normal file
@ -0,0 +1,312 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/log"
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
)
|
||||
|
||||
const (
|
||||
maxReplBatchNum = 100
|
||||
maxReplLogSize = 1 * 1024 * 1024
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSkipEvent = errors.New("skip to next event")
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidBinLogEvent = errors.New("invalid binglog event")
|
||||
errInvalidBinLogFile = errors.New("invalid binlog file")
|
||||
)
|
||||
|
||||
type replBatch struct {
|
||||
wb driver.IWriteBatch
|
||||
events [][]byte
|
||||
l *Nodb
|
||||
|
||||
lastHead *BinLogHead
|
||||
}
|
||||
|
||||
func (b *replBatch) Commit() error {
|
||||
b.l.commitLock.Lock()
|
||||
defer b.l.commitLock.Unlock()
|
||||
|
||||
err := b.wb.Commit()
|
||||
if err != nil {
|
||||
b.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if b.l.binlog != nil {
|
||||
if err = b.l.binlog.Log(b.events...); err != nil {
|
||||
b.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
b.events = [][]byte{}
|
||||
b.lastHead = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *replBatch) Rollback() error {
|
||||
b.wb.Rollback()
|
||||
b.events = [][]byte{}
|
||||
b.lastHead = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Nodb) replicateEvent(b *replBatch, event []byte) error {
|
||||
if len(event) == 0 {
|
||||
return errInvalidBinLogEvent
|
||||
}
|
||||
|
||||
b.events = append(b.events, event)
|
||||
|
||||
logType := uint8(event[0])
|
||||
switch logType {
|
||||
case BinLogTypePut:
|
||||
return l.replicatePutEvent(b, event)
|
||||
case BinLogTypeDeletion:
|
||||
return l.replicateDeleteEvent(b, event)
|
||||
default:
|
||||
return errInvalidBinLogEvent
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Nodb) replicatePutEvent(b *replBatch, event []byte) error {
|
||||
key, value, err := decodeBinLogPut(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.wb.Put(key, value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Nodb) replicateDeleteEvent(b *replBatch, event []byte) error {
|
||||
key, err := decodeBinLogDelete(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.wb.Delete(key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadEventFromReader(rb io.Reader, f func(head *BinLogHead, event []byte) error) error {
|
||||
head := &BinLogHead{}
|
||||
var err error
|
||||
|
||||
for {
|
||||
if err = head.Read(rb); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var dataBuf bytes.Buffer
|
||||
|
||||
if _, err = io.CopyN(&dataBuf, rb, int64(head.PayloadLen)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = f(head, dataBuf.Bytes())
|
||||
if err != nil && err != ErrSkipEvent {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Nodb) ReplicateFromReader(rb io.Reader) error {
|
||||
b := new(replBatch)
|
||||
|
||||
b.wb = l.ldb.NewWriteBatch()
|
||||
b.l = l
|
||||
|
||||
f := func(head *BinLogHead, event []byte) error {
|
||||
if b.lastHead == nil {
|
||||
b.lastHead = head
|
||||
} else if !b.lastHead.InSameBatch(head) {
|
||||
if err := b.Commit(); err != nil {
|
||||
log.Fatal("replication error %s, skip to next", err.Error())
|
||||
return ErrSkipEvent
|
||||
}
|
||||
b.lastHead = head
|
||||
}
|
||||
|
||||
err := l.replicateEvent(b, event)
|
||||
if err != nil {
|
||||
log.Fatal("replication error %s, skip to next", err.Error())
|
||||
return ErrSkipEvent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := ReadEventFromReader(rb, f)
|
||||
if err != nil {
|
||||
b.Rollback()
|
||||
return err
|
||||
}
|
||||
return b.Commit()
|
||||
}
|
||||
|
||||
func (l *Nodb) ReplicateFromData(data []byte) error {
|
||||
rb := bytes.NewReader(data)
|
||||
|
||||
err := l.ReplicateFromReader(rb)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *Nodb) ReplicateFromBinLog(filePath string) error {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rb := bufio.NewReaderSize(f, 4096)
|
||||
|
||||
err = l.ReplicateFromReader(rb)
|
||||
|
||||
f.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// try to read events, if no events read, try to wait the new event singal until timeout seconds
|
||||
func (l *Nodb) ReadEventsToTimeout(info *BinLogAnchor, w io.Writer, timeout int) (n int, err error) {
|
||||
lastIndex := info.LogFileIndex
|
||||
lastPos := info.LogPos
|
||||
|
||||
n = 0
|
||||
if l.binlog == nil {
|
||||
//binlog not supported
|
||||
info.LogFileIndex = 0
|
||||
info.LogPos = 0
|
||||
return
|
||||
}
|
||||
|
||||
n, err = l.ReadEventsTo(info, w)
|
||||
if err == nil && info.LogFileIndex == lastIndex && info.LogPos == lastPos {
|
||||
//no events read
|
||||
select {
|
||||
case <-l.binlog.Wait():
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
}
|
||||
return l.ReadEventsTo(info, w)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Nodb) ReadEventsTo(info *BinLogAnchor, w io.Writer) (n int, err error) {
|
||||
n = 0
|
||||
if l.binlog == nil {
|
||||
//binlog not supported
|
||||
info.LogFileIndex = 0
|
||||
info.LogPos = 0
|
||||
return
|
||||
}
|
||||
|
||||
index := info.LogFileIndex
|
||||
offset := info.LogPos
|
||||
|
||||
filePath := l.binlog.FormatLogFilePath(index)
|
||||
|
||||
var f *os.File
|
||||
f, err = os.Open(filePath)
|
||||
if os.IsNotExist(err) {
|
||||
lastIndex := l.binlog.LogFileIndex()
|
||||
|
||||
if index == lastIndex {
|
||||
//no binlog at all
|
||||
info.LogPos = 0
|
||||
} else {
|
||||
//slave binlog info had lost
|
||||
info.LogFileIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
var fileSize int64
|
||||
st, _ := f.Stat()
|
||||
fileSize = st.Size()
|
||||
|
||||
if fileSize == info.LogPos {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = f.Seek(offset, os.SEEK_SET); err != nil {
|
||||
//may be invliad seek offset
|
||||
return
|
||||
}
|
||||
|
||||
var lastHead *BinLogHead = nil
|
||||
|
||||
head := &BinLogHead{}
|
||||
|
||||
batchNum := 0
|
||||
|
||||
for {
|
||||
if err = head.Read(f); err != nil {
|
||||
if err == io.EOF {
|
||||
//we will try to use next binlog
|
||||
if index < l.binlog.LogFileIndex() {
|
||||
info.LogFileIndex += 1
|
||||
info.LogPos = 0
|
||||
}
|
||||
err = nil
|
||||
return
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if lastHead == nil {
|
||||
lastHead = head
|
||||
batchNum++
|
||||
} else if !lastHead.InSameBatch(head) {
|
||||
lastHead = head
|
||||
batchNum++
|
||||
if batchNum > maxReplBatchNum || n > maxReplLogSize {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = head.Write(w); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = io.CopyN(w, f, int64(head.PayloadLen)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n += (head.Len() + int(head.PayloadLen))
|
||||
info.LogPos = info.LogPos + int64(head.Len()) + int64(head.PayloadLen)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
144
vendor/github.com/lunny/nodb/scan.go
generated
vendored
Normal file
144
vendor/github.com/lunny/nodb/scan.go
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"regexp"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
var errDataType = errors.New("error data type")
|
||||
var errMetaKey = errors.New("error meta key")
|
||||
|
||||
// Seek search the prefix key
|
||||
func (db *DB) Seek(key []byte) (*store.Iterator, error) {
|
||||
return db.seek(KVType, key)
|
||||
}
|
||||
|
||||
func (db *DB) seek(dataType byte, key []byte) (*store.Iterator, error) {
|
||||
var minKey []byte
|
||||
var err error
|
||||
|
||||
if len(key) > 0 {
|
||||
if err = checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if minKey, err = db.encodeMetaKey(dataType, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
if minKey, err = db.encodeMinKey(dataType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
it.Seek(minKey)
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (db *DB) MaxKey() ([]byte, error) {
|
||||
return db.encodeMaxKey(KVType)
|
||||
}
|
||||
|
||||
func (db *DB) Key(it *store.Iterator) ([]byte, error) {
|
||||
return db.decodeMetaKey(KVType, it.Key())
|
||||
}
|
||||
|
||||
func (db *DB) scan(dataType byte, key []byte, count int, inclusive bool, match string) ([][]byte, error) {
|
||||
var minKey, maxKey []byte
|
||||
var err error
|
||||
var r *regexp.Regexp
|
||||
|
||||
if len(match) > 0 {
|
||||
if r, err = regexp.Compile(match); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(key) > 0 {
|
||||
if err = checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if minKey, err = db.encodeMetaKey(dataType, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
if minKey, err = db.encodeMinKey(dataType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if maxKey, err = db.encodeMaxKey(dataType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count <= 0 {
|
||||
count = defaultScanCount
|
||||
}
|
||||
|
||||
v := make([][]byte, 0, count)
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
it.Seek(minKey)
|
||||
|
||||
if !inclusive {
|
||||
if it.Valid() && bytes.Equal(it.RawKey(), minKey) {
|
||||
it.Next()
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; it.Valid() && i < count && bytes.Compare(it.RawKey(), maxKey) < 0; it.Next() {
|
||||
if k, err := db.decodeMetaKey(dataType, it.Key()); err != nil {
|
||||
continue
|
||||
} else if r != nil && !r.Match(k) {
|
||||
continue
|
||||
} else {
|
||||
v = append(v, k)
|
||||
i++
|
||||
}
|
||||
}
|
||||
it.Close()
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) encodeMinKey(dataType byte) ([]byte, error) {
|
||||
return db.encodeMetaKey(dataType, nil)
|
||||
}
|
||||
|
||||
func (db *DB) encodeMaxKey(dataType byte) ([]byte, error) {
|
||||
k, err := db.encodeMetaKey(dataType, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
k[len(k)-1] = dataType + 1
|
||||
return k, nil
|
||||
}
|
||||
|
||||
func (db *DB) encodeMetaKey(dataType byte, key []byte) ([]byte, error) {
|
||||
switch dataType {
|
||||
case KVType:
|
||||
return db.encodeKVKey(key), nil
|
||||
case LMetaType:
|
||||
return db.lEncodeMetaKey(key), nil
|
||||
case HSizeType:
|
||||
return db.hEncodeSizeKey(key), nil
|
||||
case ZSizeType:
|
||||
return db.zEncodeSizeKey(key), nil
|
||||
case BitMetaType:
|
||||
return db.bEncodeMetaKey(key), nil
|
||||
case SSizeType:
|
||||
return db.sEncodeSizeKey(key), nil
|
||||
default:
|
||||
return nil, errDataType
|
||||
}
|
||||
}
|
||||
func (db *DB) decodeMetaKey(dataType byte, ek []byte) ([]byte, error) {
|
||||
if len(ek) < 2 || ek[0] != db.index || ek[1] != dataType {
|
||||
return nil, errMetaKey
|
||||
}
|
||||
return ek[2:], nil
|
||||
}
|
61
vendor/github.com/lunny/nodb/store/db.go
generated
vendored
Normal file
61
vendor/github.com/lunny/nodb/store/db.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
driver.IDB
|
||||
}
|
||||
|
||||
func (db *DB) NewIterator() *Iterator {
|
||||
it := new(Iterator)
|
||||
it.it = db.IDB.NewIterator()
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
func (db *DB) NewWriteBatch() WriteBatch {
|
||||
return db.IDB.NewWriteBatch()
|
||||
}
|
||||
|
||||
func (db *DB) NewSnapshot() (*Snapshot, error) {
|
||||
var err error
|
||||
s := &Snapshot{}
|
||||
if s.ISnapshot, err = db.IDB.NewSnapshot(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (db *DB) RangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator {
|
||||
return NewRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1})
|
||||
}
|
||||
|
||||
func (db *DB) RevRangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator {
|
||||
return NewRevRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1})
|
||||
}
|
||||
|
||||
//count < 0, unlimit.
|
||||
//
|
||||
//offset must >= 0, if < 0, will get nothing.
|
||||
func (db *DB) RangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *RangeLimitIterator {
|
||||
return NewRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{offset, count})
|
||||
}
|
||||
|
||||
//count < 0, unlimit.
|
||||
//
|
||||
//offset must >= 0, if < 0, will get nothing.
|
||||
func (db *DB) RevRangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *RangeLimitIterator {
|
||||
return NewRevRangeLimitIterator(db.NewIterator(), &Range{min, max, rangeType}, &Limit{offset, count})
|
||||
}
|
||||
|
||||
func (db *DB) Begin() (*Tx, error) {
|
||||
tx, err := db.IDB.Begin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Tx{tx}, nil
|
||||
}
|
39
vendor/github.com/lunny/nodb/store/driver/batch.go
generated
vendored
Normal file
39
vendor/github.com/lunny/nodb/store/driver/batch.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package driver
|
||||
|
||||
type BatchPuter interface {
|
||||
BatchPut([]Write) error
|
||||
}
|
||||
|
||||
type Write struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
type WriteBatch struct {
|
||||
batch BatchPuter
|
||||
wb []Write
|
||||
}
|
||||
|
||||
func (w *WriteBatch) Put(key, value []byte) {
|
||||
if value == nil {
|
||||
value = []byte{}
|
||||
}
|
||||
w.wb = append(w.wb, Write{key, value})
|
||||
}
|
||||
|
||||
func (w *WriteBatch) Delete(key []byte) {
|
||||
w.wb = append(w.wb, Write{key, nil})
|
||||
}
|
||||
|
||||
func (w *WriteBatch) Commit() error {
|
||||
return w.batch.BatchPut(w.wb)
|
||||
}
|
||||
|
||||
func (w *WriteBatch) Rollback() error {
|
||||
w.wb = w.wb[0:0]
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWriteBatch(puter BatchPuter) IWriteBatch {
|
||||
return &WriteBatch{puter, []Write{}}
|
||||
}
|
67
vendor/github.com/lunny/nodb/store/driver/driver.go
generated
vendored
Normal file
67
vendor/github.com/lunny/nodb/store/driver/driver.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTxSupport = errors.New("transaction is not supported")
|
||||
)
|
||||
|
||||
type IDB interface {
|
||||
Close() error
|
||||
|
||||
Get(key []byte) ([]byte, error)
|
||||
|
||||
Put(key []byte, value []byte) error
|
||||
Delete(key []byte) error
|
||||
|
||||
NewIterator() IIterator
|
||||
|
||||
NewWriteBatch() IWriteBatch
|
||||
|
||||
NewSnapshot() (ISnapshot, error)
|
||||
|
||||
Begin() (Tx, error)
|
||||
}
|
||||
|
||||
type ISnapshot interface {
|
||||
Get(key []byte) ([]byte, error)
|
||||
NewIterator() IIterator
|
||||
Close()
|
||||
}
|
||||
|
||||
type IIterator interface {
|
||||
Close() error
|
||||
|
||||
First()
|
||||
Last()
|
||||
Seek(key []byte)
|
||||
|
||||
Next()
|
||||
Prev()
|
||||
|
||||
Valid() bool
|
||||
|
||||
Key() []byte
|
||||
Value() []byte
|
||||
}
|
||||
|
||||
type IWriteBatch interface {
|
||||
Put(key []byte, value []byte)
|
||||
Delete(key []byte)
|
||||
Commit() error
|
||||
Rollback() error
|
||||
}
|
||||
|
||||
type Tx interface {
|
||||
Get(key []byte) ([]byte, error)
|
||||
Put(key []byte, value []byte) error
|
||||
Delete(key []byte) error
|
||||
|
||||
NewIterator() IIterator
|
||||
NewWriteBatch() IWriteBatch
|
||||
|
||||
Commit() error
|
||||
Rollback() error
|
||||
}
|
46
vendor/github.com/lunny/nodb/store/driver/store.go
generated
vendored
Normal file
46
vendor/github.com/lunny/nodb/store/driver/store.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package driver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lunny/nodb/config"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
String() string
|
||||
Open(path string, cfg *config.Config) (IDB, error)
|
||||
Repair(path string, cfg *config.Config) error
|
||||
}
|
||||
|
||||
var dbs = map[string]Store{}
|
||||
|
||||
func Register(s Store) {
|
||||
name := s.String()
|
||||
if _, ok := dbs[name]; ok {
|
||||
panic(fmt.Errorf("store %s is registered", s))
|
||||
}
|
||||
|
||||
dbs[name] = s
|
||||
}
|
||||
|
||||
func ListStores() []string {
|
||||
s := []string{}
|
||||
for k, _ := range dbs {
|
||||
s = append(s, k)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func GetStore(cfg *config.Config) (Store, error) {
|
||||
if len(cfg.DBName) == 0 {
|
||||
cfg.DBName = config.DefaultDBName
|
||||
}
|
||||
|
||||
s, ok := dbs[cfg.DBName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("store %s is not registered", cfg.DBName)
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
27
vendor/github.com/lunny/nodb/store/goleveldb/batch.go
generated
vendored
Normal file
27
vendor/github.com/lunny/nodb/store/goleveldb/batch.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package goleveldb
|
||||
|
||||
import (
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
type WriteBatch struct {
|
||||
db *DB
|
||||
wbatch *leveldb.Batch
|
||||
}
|
||||
|
||||
func (w *WriteBatch) Put(key, value []byte) {
|
||||
w.wbatch.Put(key, value)
|
||||
}
|
||||
|
||||
func (w *WriteBatch) Delete(key []byte) {
|
||||
w.wbatch.Delete(key)
|
||||
}
|
||||
|
||||
func (w *WriteBatch) Commit() error {
|
||||
return w.db.db.Write(w.wbatch, nil)
|
||||
}
|
||||
|
||||
func (w *WriteBatch) Rollback() error {
|
||||
w.wbatch.Reset()
|
||||
return nil
|
||||
}
|
4
vendor/github.com/lunny/nodb/store/goleveldb/const.go
generated
vendored
Normal file
4
vendor/github.com/lunny/nodb/store/goleveldb/const.go
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
package goleveldb
|
||||
|
||||
const DBName = "goleveldb"
|
||||
const MemDBName = "memory"
|
187
vendor/github.com/lunny/nodb/store/goleveldb/db.go
generated
vendored
Normal file
187
vendor/github.com/lunny/nodb/store/goleveldb/db.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
||||
package goleveldb
|
||||
|
||||
import (
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/cache"
|
||||
"github.com/syndtr/goleveldb/leveldb/filter"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
|
||||
"github.com/lunny/nodb/config"
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
|
||||
"os"
|
||||
)
|
||||
|
||||
const defaultFilterBits int = 10
|
||||
|
||||
type Store struct {
|
||||
}
|
||||
|
||||
func (s Store) String() string {
|
||||
return DBName
|
||||
}
|
||||
|
||||
type MemStore struct {
|
||||
}
|
||||
|
||||
func (s MemStore) String() string {
|
||||
return MemDBName
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
path string
|
||||
|
||||
cfg *config.LevelDBConfig
|
||||
|
||||
db *leveldb.DB
|
||||
|
||||
opts *opt.Options
|
||||
|
||||
iteratorOpts *opt.ReadOptions
|
||||
|
||||
cache cache.Cache
|
||||
|
||||
filter filter.Filter
|
||||
}
|
||||
|
||||
func (s Store) Open(path string, cfg *config.Config) (driver.IDB, error) {
|
||||
if err := os.MkdirAll(path, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db := new(DB)
|
||||
db.path = path
|
||||
db.cfg = &cfg.LevelDB
|
||||
|
||||
db.initOpts()
|
||||
|
||||
var err error
|
||||
db.db, err = leveldb.OpenFile(db.path, db.opts)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (s Store) Repair(path string, cfg *config.Config) error {
|
||||
db, err := leveldb.RecoverFile(path, newOptions(&cfg.LevelDB))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s MemStore) Open(path string, cfg *config.Config) (driver.IDB, error) {
|
||||
db := new(DB)
|
||||
db.path = path
|
||||
db.cfg = &cfg.LevelDB
|
||||
|
||||
db.initOpts()
|
||||
|
||||
var err error
|
||||
db.db, err = leveldb.Open(storage.NewMemStorage(), db.opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (s MemStore) Repair(path string, cfg *config.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) initOpts() {
|
||||
db.opts = newOptions(db.cfg)
|
||||
|
||||
db.iteratorOpts = &opt.ReadOptions{}
|
||||
db.iteratorOpts.DontFillCache = true
|
||||
}
|
||||
|
||||
func newOptions(cfg *config.LevelDBConfig) *opt.Options {
|
||||
opts := &opt.Options{}
|
||||
opts.ErrorIfMissing = false
|
||||
|
||||
cfg.Adjust()
|
||||
|
||||
//opts.BlockCacher = cache.NewLRU(cfg.CacheSize)
|
||||
opts.BlockCacheCapacity = cfg.CacheSize
|
||||
|
||||
//we must use bloomfilter
|
||||
opts.Filter = filter.NewBloomFilter(defaultFilterBits)
|
||||
|
||||
if !cfg.Compression {
|
||||
opts.Compression = opt.NoCompression
|
||||
} else {
|
||||
opts.Compression = opt.SnappyCompression
|
||||
}
|
||||
|
||||
opts.BlockSize = cfg.BlockSize
|
||||
opts.WriteBuffer = cfg.WriteBufferSize
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func (db *DB) Close() error {
|
||||
return db.db.Close()
|
||||
}
|
||||
|
||||
func (db *DB) Put(key, value []byte) error {
|
||||
return db.db.Put(key, value, nil)
|
||||
}
|
||||
|
||||
func (db *DB) Get(key []byte) ([]byte, error) {
|
||||
v, err := db.db.Get(key, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) Delete(key []byte) error {
|
||||
return db.db.Delete(key, nil)
|
||||
}
|
||||
|
||||
func (db *DB) NewWriteBatch() driver.IWriteBatch {
|
||||
wb := &WriteBatch{
|
||||
db: db,
|
||||
wbatch: new(leveldb.Batch),
|
||||
}
|
||||
return wb
|
||||
}
|
||||
|
||||
func (db *DB) NewIterator() driver.IIterator {
|
||||
it := &Iterator{
|
||||
db.db.NewIterator(nil, db.iteratorOpts),
|
||||
}
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
func (db *DB) Begin() (driver.Tx, error) {
|
||||
return nil, driver.ErrTxSupport
|
||||
}
|
||||
|
||||
func (db *DB) NewSnapshot() (driver.ISnapshot, error) {
|
||||
snapshot, err := db.db.GetSnapshot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Snapshot{
|
||||
db: db,
|
||||
snp: snapshot,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
driver.Register(Store{})
|
||||
driver.Register(MemStore{})
|
||||
}
|
49
vendor/github.com/lunny/nodb/store/goleveldb/iterator.go
generated
vendored
Normal file
49
vendor/github.com/lunny/nodb/store/goleveldb/iterator.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package goleveldb
|
||||
|
||||
import (
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
)
|
||||
|
||||
type Iterator struct {
|
||||
it iterator.Iterator
|
||||
}
|
||||
|
||||
func (it *Iterator) Key() []byte {
|
||||
return it.it.Key()
|
||||
}
|
||||
|
||||
func (it *Iterator) Value() []byte {
|
||||
return it.it.Value()
|
||||
}
|
||||
|
||||
func (it *Iterator) Close() error {
|
||||
if it.it != nil {
|
||||
it.it.Release()
|
||||
it.it = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *Iterator) Valid() bool {
|
||||
return it.it.Valid()
|
||||
}
|
||||
|
||||
func (it *Iterator) Next() {
|
||||
it.it.Next()
|
||||
}
|
||||
|
||||
func (it *Iterator) Prev() {
|
||||
it.it.Prev()
|
||||
}
|
||||
|
||||
func (it *Iterator) First() {
|
||||
it.it.First()
|
||||
}
|
||||
|
||||
func (it *Iterator) Last() {
|
||||
it.it.Last()
|
||||
}
|
||||
|
||||
func (it *Iterator) Seek(key []byte) {
|
||||
it.it.Seek(key)
|
||||
}
|
26
vendor/github.com/lunny/nodb/store/goleveldb/snapshot.go
generated
vendored
Normal file
26
vendor/github.com/lunny/nodb/store/goleveldb/snapshot.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
package goleveldb
|
||||
|
||||
import (
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
type Snapshot struct {
|
||||
db *DB
|
||||
snp *leveldb.Snapshot
|
||||
}
|
||||
|
||||
func (s *Snapshot) Get(key []byte) ([]byte, error) {
|
||||
return s.snp.Get(key, s.db.iteratorOpts)
|
||||
}
|
||||
|
||||
func (s *Snapshot) NewIterator() driver.IIterator {
|
||||
it := &Iterator{
|
||||
s.snp.NewIterator(nil, s.db.iteratorOpts),
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func (s *Snapshot) Close() {
|
||||
s.snp.Release()
|
||||
}
|
327
vendor/github.com/lunny/nodb/store/iterator.go
generated
vendored
Normal file
327
vendor/github.com/lunny/nodb/store/iterator.go
generated
vendored
Normal file
@ -0,0 +1,327 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
)
|
||||
|
||||
const (
|
||||
IteratorForward uint8 = 0
|
||||
IteratorBackward uint8 = 1
|
||||
)
|
||||
|
||||
const (
|
||||
RangeClose uint8 = 0x00
|
||||
RangeLOpen uint8 = 0x01
|
||||
RangeROpen uint8 = 0x10
|
||||
RangeOpen uint8 = 0x11
|
||||
)
|
||||
|
||||
// min must less or equal than max
|
||||
//
|
||||
// range type:
|
||||
//
|
||||
// close: [min, max]
|
||||
// open: (min, max)
|
||||
// lopen: (min, max]
|
||||
// ropen: [min, max)
|
||||
//
|
||||
type Range struct {
|
||||
Min []byte
|
||||
Max []byte
|
||||
|
||||
Type uint8
|
||||
}
|
||||
|
||||
type Limit struct {
|
||||
Offset int
|
||||
Count int
|
||||
}
|
||||
|
||||
type Iterator struct {
|
||||
it driver.IIterator
|
||||
}
|
||||
|
||||
// Returns a copy of key.
|
||||
func (it *Iterator) Key() []byte {
|
||||
k := it.it.Key()
|
||||
if k == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return append([]byte{}, k...)
|
||||
}
|
||||
|
||||
// Returns a copy of value.
|
||||
func (it *Iterator) Value() []byte {
|
||||
v := it.it.Value()
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return append([]byte{}, v...)
|
||||
}
|
||||
|
||||
// Returns a reference of key.
|
||||
// you must be careful that it will be changed after next iterate.
|
||||
func (it *Iterator) RawKey() []byte {
|
||||
return it.it.Key()
|
||||
}
|
||||
|
||||
// Returns a reference of value.
|
||||
// you must be careful that it will be changed after next iterate.
|
||||
func (it *Iterator) RawValue() []byte {
|
||||
return it.it.Value()
|
||||
}
|
||||
|
||||
// Copy key to b, if b len is small or nil, returns a new one.
|
||||
func (it *Iterator) BufKey(b []byte) []byte {
|
||||
k := it.RawKey()
|
||||
if k == nil {
|
||||
return nil
|
||||
}
|
||||
if b == nil {
|
||||
b = []byte{}
|
||||
}
|
||||
|
||||
b = b[0:0]
|
||||
return append(b, k...)
|
||||
}
|
||||
|
||||
// Copy value to b, if b len is small or nil, returns a new one.
|
||||
func (it *Iterator) BufValue(b []byte) []byte {
|
||||
v := it.RawValue()
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if b == nil {
|
||||
b = []byte{}
|
||||
}
|
||||
|
||||
b = b[0:0]
|
||||
return append(b, v...)
|
||||
}
|
||||
|
||||
func (it *Iterator) Close() {
|
||||
if it.it != nil {
|
||||
it.it.Close()
|
||||
it.it = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (it *Iterator) Valid() bool {
|
||||
return it.it.Valid()
|
||||
}
|
||||
|
||||
func (it *Iterator) Next() {
|
||||
it.it.Next()
|
||||
}
|
||||
|
||||
func (it *Iterator) Prev() {
|
||||
it.it.Prev()
|
||||
}
|
||||
|
||||
func (it *Iterator) SeekToFirst() {
|
||||
it.it.First()
|
||||
}
|
||||
|
||||
func (it *Iterator) SeekToLast() {
|
||||
it.it.Last()
|
||||
}
|
||||
|
||||
func (it *Iterator) Seek(key []byte) {
|
||||
it.it.Seek(key)
|
||||
}
|
||||
|
||||
// Finds by key, if not found, nil returns.
|
||||
func (it *Iterator) Find(key []byte) []byte {
|
||||
it.Seek(key)
|
||||
if it.Valid() {
|
||||
k := it.RawKey()
|
||||
if k == nil {
|
||||
return nil
|
||||
} else if bytes.Equal(k, key) {
|
||||
return it.Value()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finds by key, if not found, nil returns, else a reference of value returns.
|
||||
// you must be careful that it will be changed after next iterate.
|
||||
func (it *Iterator) RawFind(key []byte) []byte {
|
||||
it.Seek(key)
|
||||
if it.Valid() {
|
||||
k := it.RawKey()
|
||||
if k == nil {
|
||||
return nil
|
||||
} else if bytes.Equal(k, key) {
|
||||
return it.RawValue()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type RangeLimitIterator struct {
|
||||
it *Iterator
|
||||
|
||||
r *Range
|
||||
l *Limit
|
||||
|
||||
step int
|
||||
|
||||
//0 for IteratorForward, 1 for IteratorBackward
|
||||
direction uint8
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) Key() []byte {
|
||||
return it.it.Key()
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) Value() []byte {
|
||||
return it.it.Value()
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) RawKey() []byte {
|
||||
return it.it.RawKey()
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) RawValue() []byte {
|
||||
return it.it.RawValue()
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) BufKey(b []byte) []byte {
|
||||
return it.it.BufKey(b)
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) BufValue(b []byte) []byte {
|
||||
return it.it.BufValue(b)
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) Valid() bool {
|
||||
if it.l.Offset < 0 {
|
||||
return false
|
||||
} else if !it.it.Valid() {
|
||||
return false
|
||||
} else if it.l.Count >= 0 && it.step >= it.l.Count {
|
||||
return false
|
||||
}
|
||||
|
||||
if it.direction == IteratorForward {
|
||||
if it.r.Max != nil {
|
||||
r := bytes.Compare(it.it.RawKey(), it.r.Max)
|
||||
if it.r.Type&RangeROpen > 0 {
|
||||
return !(r >= 0)
|
||||
} else {
|
||||
return !(r > 0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if it.r.Min != nil {
|
||||
r := bytes.Compare(it.it.RawKey(), it.r.Min)
|
||||
if it.r.Type&RangeLOpen > 0 {
|
||||
return !(r <= 0)
|
||||
} else {
|
||||
return !(r < 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) Next() {
|
||||
it.step++
|
||||
|
||||
if it.direction == IteratorForward {
|
||||
it.it.Next()
|
||||
} else {
|
||||
it.it.Prev()
|
||||
}
|
||||
}
|
||||
|
||||
func (it *RangeLimitIterator) Close() {
|
||||
it.it.Close()
|
||||
}
|
||||
|
||||
func NewRangeLimitIterator(i *Iterator, r *Range, l *Limit) *RangeLimitIterator {
|
||||
return rangeLimitIterator(i, r, l, IteratorForward)
|
||||
}
|
||||
|
||||
func NewRevRangeLimitIterator(i *Iterator, r *Range, l *Limit) *RangeLimitIterator {
|
||||
return rangeLimitIterator(i, r, l, IteratorBackward)
|
||||
}
|
||||
|
||||
func NewRangeIterator(i *Iterator, r *Range) *RangeLimitIterator {
|
||||
return rangeLimitIterator(i, r, &Limit{0, -1}, IteratorForward)
|
||||
}
|
||||
|
||||
func NewRevRangeIterator(i *Iterator, r *Range) *RangeLimitIterator {
|
||||
return rangeLimitIterator(i, r, &Limit{0, -1}, IteratorBackward)
|
||||
}
|
||||
|
||||
func rangeLimitIterator(i *Iterator, r *Range, l *Limit, direction uint8) *RangeLimitIterator {
|
||||
it := new(RangeLimitIterator)
|
||||
|
||||
it.it = i
|
||||
|
||||
it.r = r
|
||||
it.l = l
|
||||
it.direction = direction
|
||||
|
||||
it.step = 0
|
||||
|
||||
if l.Offset < 0 {
|
||||
return it
|
||||
}
|
||||
|
||||
if direction == IteratorForward {
|
||||
if r.Min == nil {
|
||||
it.it.SeekToFirst()
|
||||
} else {
|
||||
it.it.Seek(r.Min)
|
||||
|
||||
if r.Type&RangeLOpen > 0 {
|
||||
if it.it.Valid() && bytes.Equal(it.it.RawKey(), r.Min) {
|
||||
it.it.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if r.Max == nil {
|
||||
it.it.SeekToLast()
|
||||
} else {
|
||||
it.it.Seek(r.Max)
|
||||
|
||||
if !it.it.Valid() {
|
||||
it.it.SeekToLast()
|
||||
} else {
|
||||
if !bytes.Equal(it.it.RawKey(), r.Max) {
|
||||
it.it.Prev()
|
||||
}
|
||||
}
|
||||
|
||||
if r.Type&RangeROpen > 0 {
|
||||
if it.it.Valid() && bytes.Equal(it.it.RawKey(), r.Max) {
|
||||
it.it.Prev()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < l.Offset; i++ {
|
||||
if it.it.Valid() {
|
||||
if it.direction == IteratorForward {
|
||||
it.it.Next()
|
||||
} else {
|
||||
it.it.Prev()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it
|
||||
}
|
16
vendor/github.com/lunny/nodb/store/snapshot.go
generated
vendored
Normal file
16
vendor/github.com/lunny/nodb/store/snapshot.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
)
|
||||
|
||||
type Snapshot struct {
|
||||
driver.ISnapshot
|
||||
}
|
||||
|
||||
func (s *Snapshot) NewIterator() *Iterator {
|
||||
it := new(Iterator)
|
||||
it.it = s.ISnapshot.NewIterator()
|
||||
|
||||
return it
|
||||
}
|
51
vendor/github.com/lunny/nodb/store/store.go
generated
vendored
Normal file
51
vendor/github.com/lunny/nodb/store/store.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"github.com/lunny/nodb/config"
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
|
||||
_ "github.com/lunny/nodb/store/goleveldb"
|
||||
)
|
||||
|
||||
func getStorePath(cfg *config.Config) string {
|
||||
return path.Join(cfg.DataDir, fmt.Sprintf("%s_data", cfg.DBName))
|
||||
}
|
||||
|
||||
func Open(cfg *config.Config) (*DB, error) {
|
||||
s, err := driver.GetStore(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
path := getStorePath(cfg)
|
||||
|
||||
if err := os.MkdirAll(path, os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idb, err := s.Open(path, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db := &DB{idb}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func Repair(cfg *config.Config) error {
|
||||
s, err := driver.GetStore(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path := getStorePath(cfg)
|
||||
|
||||
return s.Repair(path, cfg)
|
||||
}
|
||||
|
||||
func init() {
|
||||
}
|
42
vendor/github.com/lunny/nodb/store/tx.go
generated
vendored
Normal file
42
vendor/github.com/lunny/nodb/store/tx.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
)
|
||||
|
||||
type Tx struct {
|
||||
driver.Tx
|
||||
}
|
||||
|
||||
func (tx *Tx) NewIterator() *Iterator {
|
||||
it := new(Iterator)
|
||||
it.it = tx.Tx.NewIterator()
|
||||
|
||||
return it
|
||||
}
|
||||
|
||||
func (tx *Tx) NewWriteBatch() WriteBatch {
|
||||
return tx.Tx.NewWriteBatch()
|
||||
}
|
||||
|
||||
func (tx *Tx) RangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator {
|
||||
return NewRangeLimitIterator(tx.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1})
|
||||
}
|
||||
|
||||
func (tx *Tx) RevRangeIterator(min []byte, max []byte, rangeType uint8) *RangeLimitIterator {
|
||||
return NewRevRangeLimitIterator(tx.NewIterator(), &Range{min, max, rangeType}, &Limit{0, -1})
|
||||
}
|
||||
|
||||
//count < 0, unlimit.
|
||||
//
|
||||
//offset must >= 0, if < 0, will get nothing.
|
||||
func (tx *Tx) RangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *RangeLimitIterator {
|
||||
return NewRangeLimitIterator(tx.NewIterator(), &Range{min, max, rangeType}, &Limit{offset, count})
|
||||
}
|
||||
|
||||
//count < 0, unlimit.
|
||||
//
|
||||
//offset must >= 0, if < 0, will get nothing.
|
||||
func (tx *Tx) RevRangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *RangeLimitIterator {
|
||||
return NewRevRangeLimitIterator(tx.NewIterator(), &Range{min, max, rangeType}, &Limit{offset, count})
|
||||
}
|
9
vendor/github.com/lunny/nodb/store/writebatch.go
generated
vendored
Normal file
9
vendor/github.com/lunny/nodb/store/writebatch.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/lunny/nodb/store/driver"
|
||||
)
|
||||
|
||||
type WriteBatch interface {
|
||||
driver.IWriteBatch
|
||||
}
|
922
vendor/github.com/lunny/nodb/t_bit.go
generated
vendored
Normal file
922
vendor/github.com/lunny/nodb/t_bit.go
generated
vendored
Normal file
@ -0,0 +1,922 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
const (
|
||||
OPand uint8 = iota + 1
|
||||
OPor
|
||||
OPxor
|
||||
OPnot
|
||||
)
|
||||
|
||||
type BitPair struct {
|
||||
Pos int32
|
||||
Val uint8
|
||||
}
|
||||
|
||||
type segBitInfo struct {
|
||||
Seq uint32
|
||||
Off uint32
|
||||
Val uint8
|
||||
}
|
||||
|
||||
type segBitInfoArray []segBitInfo
|
||||
|
||||
const (
|
||||
// byte
|
||||
segByteWidth uint32 = 9
|
||||
segByteSize uint32 = 1 << segByteWidth
|
||||
|
||||
// bit
|
||||
segBitWidth uint32 = segByteWidth + 3
|
||||
segBitSize uint32 = segByteSize << 3
|
||||
|
||||
maxByteSize uint32 = 8 << 20
|
||||
maxSegCount uint32 = maxByteSize / segByteSize
|
||||
|
||||
minSeq uint32 = 0
|
||||
maxSeq uint32 = uint32((maxByteSize << 3) - 1)
|
||||
)
|
||||
|
||||
var bitsInByte = [256]int32{0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3,
|
||||
4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2, 3,
|
||||
3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4,
|
||||
5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4,
|
||||
3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4,
|
||||
5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2,
|
||||
2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3,
|
||||
4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
|
||||
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4,
|
||||
5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6,
|
||||
6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5,
|
||||
6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}
|
||||
|
||||
var fillBits = [...]uint8{1, 3, 7, 15, 31, 63, 127, 255}
|
||||
|
||||
var emptySegment []byte = make([]byte, segByteSize, segByteSize)
|
||||
|
||||
var fillSegment []byte = func() []byte {
|
||||
data := make([]byte, segByteSize, segByteSize)
|
||||
for i := uint32(0); i < segByteSize; i++ {
|
||||
data[i] = 0xff
|
||||
}
|
||||
return data
|
||||
}()
|
||||
|
||||
var errBinKey = errors.New("invalid bin key")
|
||||
var errOffset = errors.New("invalid offset")
|
||||
var errDuplicatePos = errors.New("duplicate bit pos")
|
||||
|
||||
func getBit(sz []byte, offset uint32) uint8 {
|
||||
index := offset >> 3
|
||||
if index >= uint32(len(sz)) {
|
||||
return 0 // error("overflow")
|
||||
}
|
||||
|
||||
offset -= index << 3
|
||||
return sz[index] >> offset & 1
|
||||
}
|
||||
|
||||
func setBit(sz []byte, offset uint32, val uint8) bool {
|
||||
if val != 1 && val != 0 {
|
||||
return false // error("invalid val")
|
||||
}
|
||||
|
||||
index := offset >> 3
|
||||
if index >= uint32(len(sz)) {
|
||||
return false // error("overflow")
|
||||
}
|
||||
|
||||
offset -= index << 3
|
||||
if sz[index]>>offset&1 != val {
|
||||
sz[index] ^= (1 << offset)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (datas segBitInfoArray) Len() int {
|
||||
return len(datas)
|
||||
}
|
||||
|
||||
func (datas segBitInfoArray) Less(i, j int) bool {
|
||||
res := (datas)[i].Seq < (datas)[j].Seq
|
||||
if !res && (datas)[i].Seq == (datas)[j].Seq {
|
||||
res = (datas)[i].Off < (datas)[j].Off
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (datas segBitInfoArray) Swap(i, j int) {
|
||||
datas[i], datas[j] = datas[j], datas[i]
|
||||
}
|
||||
|
||||
func (db *DB) bEncodeMetaKey(key []byte) []byte {
|
||||
mk := make([]byte, len(key)+2)
|
||||
mk[0] = db.index
|
||||
mk[1] = BitMetaType
|
||||
|
||||
copy(mk[2:], key)
|
||||
return mk
|
||||
}
|
||||
|
||||
func (db *DB) bDecodeMetaKey(bkey []byte) ([]byte, error) {
|
||||
if len(bkey) < 2 || bkey[0] != db.index || bkey[1] != BitMetaType {
|
||||
return nil, errBinKey
|
||||
}
|
||||
|
||||
return bkey[2:], nil
|
||||
}
|
||||
|
||||
func (db *DB) bEncodeBinKey(key []byte, seq uint32) []byte {
|
||||
bk := make([]byte, len(key)+8)
|
||||
|
||||
pos := 0
|
||||
bk[pos] = db.index
|
||||
pos++
|
||||
bk[pos] = BitType
|
||||
pos++
|
||||
|
||||
binary.BigEndian.PutUint16(bk[pos:], uint16(len(key)))
|
||||
pos += 2
|
||||
|
||||
copy(bk[pos:], key)
|
||||
pos += len(key)
|
||||
|
||||
binary.BigEndian.PutUint32(bk[pos:], seq)
|
||||
|
||||
return bk
|
||||
}
|
||||
|
||||
func (db *DB) bDecodeBinKey(bkey []byte) (key []byte, seq uint32, err error) {
|
||||
if len(bkey) < 8 || bkey[0] != db.index {
|
||||
err = errBinKey
|
||||
return
|
||||
}
|
||||
|
||||
keyLen := binary.BigEndian.Uint16(bkey[2:4])
|
||||
if int(keyLen+8) != len(bkey) {
|
||||
err = errBinKey
|
||||
return
|
||||
}
|
||||
|
||||
key = bkey[4 : 4+keyLen]
|
||||
seq = uint32(binary.BigEndian.Uint32(bkey[4+keyLen:]))
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) bCapByteSize(seq uint32, off uint32) uint32 {
|
||||
var offByteSize uint32 = (off >> 3) + 1
|
||||
if offByteSize > segByteSize {
|
||||
offByteSize = segByteSize
|
||||
}
|
||||
|
||||
return seq<<segByteWidth + offByteSize
|
||||
}
|
||||
|
||||
func (db *DB) bParseOffset(key []byte, offset int32) (seq uint32, off uint32, err error) {
|
||||
if offset < 0 {
|
||||
if tailSeq, tailOff, e := db.bGetMeta(key); e != nil {
|
||||
err = e
|
||||
return
|
||||
} else if tailSeq >= 0 {
|
||||
offset += int32((uint32(tailSeq)<<segBitWidth | uint32(tailOff)) + 1)
|
||||
if offset < 0 {
|
||||
err = errOffset
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
off = uint32(offset)
|
||||
|
||||
seq = off >> segBitWidth
|
||||
off &= (segBitSize - 1)
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) bGetMeta(key []byte) (tailSeq int32, tailOff int32, err error) {
|
||||
var v []byte
|
||||
|
||||
mk := db.bEncodeMetaKey(key)
|
||||
v, err = db.bucket.Get(mk)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if v != nil {
|
||||
tailSeq = int32(binary.LittleEndian.Uint32(v[0:4]))
|
||||
tailOff = int32(binary.LittleEndian.Uint32(v[4:8]))
|
||||
} else {
|
||||
tailSeq = -1
|
||||
tailOff = -1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) bSetMeta(t *batch, key []byte, tailSeq uint32, tailOff uint32) {
|
||||
ek := db.bEncodeMetaKey(key)
|
||||
|
||||
buf := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint32(buf[0:4], tailSeq)
|
||||
binary.LittleEndian.PutUint32(buf[4:8], tailOff)
|
||||
|
||||
t.Put(ek, buf)
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) bUpdateMeta(t *batch, key []byte, seq uint32, off uint32) (tailSeq uint32, tailOff uint32, err error) {
|
||||
var tseq, toff int32
|
||||
var update bool = false
|
||||
|
||||
if tseq, toff, err = db.bGetMeta(key); err != nil {
|
||||
return
|
||||
} else if tseq < 0 {
|
||||
update = true
|
||||
} else {
|
||||
tailSeq = uint32(MaxInt32(tseq, 0))
|
||||
tailOff = uint32(MaxInt32(toff, 0))
|
||||
update = (seq > tailSeq || (seq == tailSeq && off > tailOff))
|
||||
}
|
||||
|
||||
if update {
|
||||
db.bSetMeta(t, key, seq, off)
|
||||
tailSeq = seq
|
||||
tailOff = off
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) bDelete(t *batch, key []byte) (drop int64) {
|
||||
mk := db.bEncodeMetaKey(key)
|
||||
t.Delete(mk)
|
||||
|
||||
minKey := db.bEncodeBinKey(key, minSeq)
|
||||
maxKey := db.bEncodeBinKey(key, maxSeq)
|
||||
it := db.bucket.RangeIterator(minKey, maxKey, store.RangeClose)
|
||||
for ; it.Valid(); it.Next() {
|
||||
t.Delete(it.RawKey())
|
||||
drop++
|
||||
}
|
||||
it.Close()
|
||||
|
||||
return drop
|
||||
}
|
||||
|
||||
func (db *DB) bGetSegment(key []byte, seq uint32) ([]byte, []byte, error) {
|
||||
bk := db.bEncodeBinKey(key, seq)
|
||||
segment, err := db.bucket.Get(bk)
|
||||
if err != nil {
|
||||
return bk, nil, err
|
||||
}
|
||||
return bk, segment, nil
|
||||
}
|
||||
|
||||
func (db *DB) bAllocateSegment(key []byte, seq uint32) ([]byte, []byte, error) {
|
||||
bk, segment, err := db.bGetSegment(key, seq)
|
||||
if err == nil && segment == nil {
|
||||
segment = make([]byte, segByteSize, segByteSize)
|
||||
}
|
||||
return bk, segment, err
|
||||
}
|
||||
|
||||
func (db *DB) bIterator(key []byte) *store.RangeLimitIterator {
|
||||
sk := db.bEncodeBinKey(key, minSeq)
|
||||
ek := db.bEncodeBinKey(key, maxSeq)
|
||||
return db.bucket.RangeIterator(sk, ek, store.RangeClose)
|
||||
}
|
||||
|
||||
func (db *DB) bSegAnd(a []byte, b []byte, res *[]byte) {
|
||||
if a == nil || b == nil {
|
||||
*res = nil
|
||||
return
|
||||
}
|
||||
|
||||
data := *res
|
||||
if data == nil {
|
||||
data = make([]byte, segByteSize, segByteSize)
|
||||
*res = data
|
||||
}
|
||||
|
||||
for i := uint32(0); i < segByteSize; i++ {
|
||||
data[i] = a[i] & b[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) bSegOr(a []byte, b []byte, res *[]byte) {
|
||||
if a == nil || b == nil {
|
||||
if a == nil && b == nil {
|
||||
*res = nil
|
||||
} else if a == nil {
|
||||
*res = b
|
||||
} else {
|
||||
*res = a
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data := *res
|
||||
if data == nil {
|
||||
data = make([]byte, segByteSize, segByteSize)
|
||||
*res = data
|
||||
}
|
||||
|
||||
for i := uint32(0); i < segByteSize; i++ {
|
||||
data[i] = a[i] | b[i]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) bSegXor(a []byte, b []byte, res *[]byte) {
|
||||
if a == nil && b == nil {
|
||||
*res = fillSegment
|
||||
return
|
||||
}
|
||||
|
||||
if a == nil {
|
||||
a = emptySegment
|
||||
}
|
||||
|
||||
if b == nil {
|
||||
b = emptySegment
|
||||
}
|
||||
|
||||
data := *res
|
||||
if data == nil {
|
||||
data = make([]byte, segByteSize, segByteSize)
|
||||
*res = data
|
||||
}
|
||||
|
||||
for i := uint32(0); i < segByteSize; i++ {
|
||||
data[i] = a[i] ^ b[i]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) bExpireAt(key []byte, when int64) (int64, error) {
|
||||
t := db.binBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if seq, _, err := db.bGetMeta(key); err != nil || seq < 0 {
|
||||
return 0, err
|
||||
} else {
|
||||
db.expireAt(t, BitType, key, when)
|
||||
if err := t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (db *DB) bCountByte(val byte, soff uint32, eoff uint32) int32 {
|
||||
if soff > eoff {
|
||||
soff, eoff = eoff, soff
|
||||
}
|
||||
|
||||
mask := uint8(0)
|
||||
if soff > 0 {
|
||||
mask |= fillBits[soff-1]
|
||||
}
|
||||
if eoff < 7 {
|
||||
mask |= (fillBits[7] ^ fillBits[eoff])
|
||||
}
|
||||
mask = fillBits[7] ^ mask
|
||||
|
||||
return bitsInByte[val&mask]
|
||||
}
|
||||
|
||||
func (db *DB) bCountSeg(key []byte, seq uint32, soff uint32, eoff uint32) (cnt int32, err error) {
|
||||
if soff >= segBitSize || soff < 0 ||
|
||||
eoff >= segBitSize || eoff < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var segment []byte
|
||||
if _, segment, err = db.bGetSegment(key, seq); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if segment == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if soff > eoff {
|
||||
soff, eoff = eoff, soff
|
||||
}
|
||||
|
||||
headIdx := int(soff >> 3)
|
||||
endIdx := int(eoff >> 3)
|
||||
sByteOff := soff - ((soff >> 3) << 3)
|
||||
eByteOff := eoff - ((eoff >> 3) << 3)
|
||||
|
||||
if headIdx == endIdx {
|
||||
cnt = db.bCountByte(segment[headIdx], sByteOff, eByteOff)
|
||||
} else {
|
||||
cnt = db.bCountByte(segment[headIdx], sByteOff, 7) +
|
||||
db.bCountByte(segment[endIdx], 0, eByteOff)
|
||||
}
|
||||
|
||||
// sum up following bytes
|
||||
for idx, end := headIdx+1, endIdx-1; idx <= end; idx += 1 {
|
||||
cnt += bitsInByte[segment[idx]]
|
||||
if idx == end {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) BGet(key []byte) (data []byte, err error) {
|
||||
if err = checkKeySize(key); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ts, to int32
|
||||
if ts, to, err = db.bGetMeta(key); err != nil || ts < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var tailSeq, tailOff = uint32(ts), uint32(to)
|
||||
var capByteSize uint32 = db.bCapByteSize(tailSeq, tailOff)
|
||||
data = make([]byte, capByteSize, capByteSize)
|
||||
|
||||
minKey := db.bEncodeBinKey(key, minSeq)
|
||||
maxKey := db.bEncodeBinKey(key, tailSeq)
|
||||
it := db.bucket.RangeIterator(minKey, maxKey, store.RangeClose)
|
||||
|
||||
var seq, s, e uint32
|
||||
for ; it.Valid(); it.Next() {
|
||||
if _, seq, err = db.bDecodeBinKey(it.RawKey()); err != nil {
|
||||
data = nil
|
||||
break
|
||||
}
|
||||
|
||||
s = seq << segByteWidth
|
||||
e = MinUInt32(s+segByteSize, capByteSize)
|
||||
copy(data[s:e], it.RawValue())
|
||||
}
|
||||
it.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) BDelete(key []byte) (drop int64, err error) {
|
||||
if err = checkKeySize(key); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t := db.binBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
drop = db.bDelete(t, key)
|
||||
db.rmExpire(t, BitType, key)
|
||||
|
||||
err = t.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) BSetBit(key []byte, offset int32, val uint8) (ori uint8, err error) {
|
||||
if err = checkKeySize(key); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// todo : check offset
|
||||
var seq, off uint32
|
||||
if seq, off, err = db.bParseOffset(key, offset); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var bk, segment []byte
|
||||
if bk, segment, err = db.bAllocateSegment(key, seq); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if segment != nil {
|
||||
ori = getBit(segment, off)
|
||||
if setBit(segment, off, val) {
|
||||
t := db.binBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.Put(bk, segment)
|
||||
if _, _, e := db.bUpdateMeta(t, key, seq, off); e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) BMSetBit(key []byte, args ...BitPair) (place int64, err error) {
|
||||
if err = checkKeySize(key); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// (ps : so as to aviod wasting memory copy while calling db.Get() and batch.Put(),
|
||||
// here we sequence the params by pos, so that we can merge the execution of
|
||||
// diff pos setting which targets on the same segment respectively. )
|
||||
|
||||
// #1 : sequence request data
|
||||
var argCnt = len(args)
|
||||
var bitInfos segBitInfoArray = make(segBitInfoArray, argCnt)
|
||||
var seq, off uint32
|
||||
|
||||
for i, info := range args {
|
||||
if seq, off, err = db.bParseOffset(key, info.Pos); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
bitInfos[i].Seq = seq
|
||||
bitInfos[i].Off = off
|
||||
bitInfos[i].Val = info.Val
|
||||
}
|
||||
|
||||
sort.Sort(bitInfos)
|
||||
|
||||
for i := 1; i < argCnt; i++ {
|
||||
if bitInfos[i].Seq == bitInfos[i-1].Seq && bitInfos[i].Off == bitInfos[i-1].Off {
|
||||
return 0, errDuplicatePos
|
||||
}
|
||||
}
|
||||
|
||||
// #2 : execute bit set in order
|
||||
t := db.binBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var curBinKey, curSeg []byte
|
||||
var curSeq, maxSeq, maxOff uint32
|
||||
|
||||
for _, info := range bitInfos {
|
||||
if curSeg != nil && info.Seq != curSeq {
|
||||
t.Put(curBinKey, curSeg)
|
||||
curSeg = nil
|
||||
}
|
||||
|
||||
if curSeg == nil {
|
||||
curSeq = info.Seq
|
||||
if curBinKey, curSeg, err = db.bAllocateSegment(key, info.Seq); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if curSeg == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if setBit(curSeg, info.Off, info.Val) {
|
||||
maxSeq = info.Seq
|
||||
maxOff = info.Off
|
||||
place++
|
||||
}
|
||||
}
|
||||
|
||||
if curSeg != nil {
|
||||
t.Put(curBinKey, curSeg)
|
||||
}
|
||||
|
||||
// finally, update meta
|
||||
if place > 0 {
|
||||
if _, _, err = db.bUpdateMeta(t, key, maxSeq, maxOff); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) BGetBit(key []byte, offset int32) (uint8, error) {
|
||||
if seq, off, err := db.bParseOffset(key, offset); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
_, segment, err := db.bGetSegment(key, seq)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if segment == nil {
|
||||
return 0, nil
|
||||
} else {
|
||||
return getBit(segment, off), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func (db *DB) BGetRange(key []byte, start int32, end int32) ([]byte, error) {
|
||||
// section := make([]byte)
|
||||
|
||||
// return
|
||||
// }
|
||||
|
||||
func (db *DB) BCount(key []byte, start int32, end int32) (cnt int32, err error) {
|
||||
var sseq, soff uint32
|
||||
if sseq, soff, err = db.bParseOffset(key, start); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var eseq, eoff uint32
|
||||
if eseq, eoff, err = db.bParseOffset(key, end); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if sseq > eseq || (sseq == eseq && soff > eoff) {
|
||||
sseq, eseq = eseq, sseq
|
||||
soff, eoff = eoff, soff
|
||||
}
|
||||
|
||||
var segCnt int32
|
||||
if eseq == sseq {
|
||||
if segCnt, err = db.bCountSeg(key, sseq, soff, eoff); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
cnt = segCnt
|
||||
|
||||
} else {
|
||||
if segCnt, err = db.bCountSeg(key, sseq, soff, segBitSize-1); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
cnt += segCnt
|
||||
}
|
||||
|
||||
if segCnt, err = db.bCountSeg(key, eseq, 0, eoff); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
cnt += segCnt
|
||||
}
|
||||
}
|
||||
|
||||
// middle segs
|
||||
var segment []byte
|
||||
skey := db.bEncodeBinKey(key, sseq)
|
||||
ekey := db.bEncodeBinKey(key, eseq)
|
||||
|
||||
it := db.bucket.RangeIterator(skey, ekey, store.RangeOpen)
|
||||
for ; it.Valid(); it.Next() {
|
||||
segment = it.RawValue()
|
||||
for _, bt := range segment {
|
||||
cnt += bitsInByte[bt]
|
||||
}
|
||||
}
|
||||
it.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) BTail(key []byte) (int32, error) {
|
||||
// effective length of data, the highest bit-pos set in history
|
||||
tailSeq, tailOff, err := db.bGetMeta(key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
tail := int32(-1)
|
||||
if tailSeq >= 0 {
|
||||
tail = int32(uint32(tailSeq)<<segBitWidth | uint32(tailOff))
|
||||
}
|
||||
|
||||
return tail, nil
|
||||
}
|
||||
|
||||
func (db *DB) BOperation(op uint8, dstkey []byte, srckeys ...[]byte) (blen int32, err error) {
|
||||
// blen -
|
||||
// the total bit size of data stored in destination key,
|
||||
// that is equal to the size of the longest input string.
|
||||
|
||||
var exeOp func([]byte, []byte, *[]byte)
|
||||
switch op {
|
||||
case OPand:
|
||||
exeOp = db.bSegAnd
|
||||
case OPor:
|
||||
exeOp = db.bSegOr
|
||||
case OPxor, OPnot:
|
||||
exeOp = db.bSegXor
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
if dstkey == nil || srckeys == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t := db.binBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var srcKseq, srcKoff int32
|
||||
var seq, off, maxDstSeq, maxDstOff uint32
|
||||
|
||||
var keyNum int = len(srckeys)
|
||||
var validKeyNum int
|
||||
for i := 0; i < keyNum; i++ {
|
||||
if srcKseq, srcKoff, err = db.bGetMeta(srckeys[i]); err != nil {
|
||||
return
|
||||
} else if srcKseq < 0 {
|
||||
srckeys[i] = nil
|
||||
continue
|
||||
}
|
||||
|
||||
validKeyNum++
|
||||
|
||||
seq = uint32(srcKseq)
|
||||
off = uint32(srcKoff)
|
||||
if seq > maxDstSeq || (seq == maxDstSeq && off > maxDstOff) {
|
||||
maxDstSeq = seq
|
||||
maxDstOff = off
|
||||
}
|
||||
}
|
||||
|
||||
if (op == OPnot && validKeyNum != 1) ||
|
||||
(op != OPnot && validKeyNum < 2) {
|
||||
return // with not enough existing source key
|
||||
}
|
||||
|
||||
var srcIdx int
|
||||
for srcIdx = 0; srcIdx < keyNum; srcIdx++ {
|
||||
if srckeys[srcIdx] != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// init - data
|
||||
var segments = make([][]byte, maxDstSeq+1)
|
||||
|
||||
if op == OPnot {
|
||||
// ps :
|
||||
// ( ~num == num ^ 0x11111111 )
|
||||
// we init the result segments with all bit set,
|
||||
// then we can calculate through the way of 'xor'.
|
||||
|
||||
// ahead segments bin format : 1111 ... 1111
|
||||
for i := uint32(0); i < maxDstSeq; i++ {
|
||||
segments[i] = fillSegment
|
||||
}
|
||||
|
||||
// last segment bin format : 1111..1100..0000
|
||||
var tailSeg = make([]byte, segByteSize, segByteSize)
|
||||
var fillByte = fillBits[7]
|
||||
var tailSegLen = db.bCapByteSize(uint32(0), maxDstOff)
|
||||
for i := uint32(0); i < tailSegLen-1; i++ {
|
||||
tailSeg[i] = fillByte
|
||||
}
|
||||
tailSeg[tailSegLen-1] = fillBits[maxDstOff-(tailSegLen-1)<<3]
|
||||
segments[maxDstSeq] = tailSeg
|
||||
|
||||
} else {
|
||||
// ps : init segments by data corresponding to the 1st valid source key
|
||||
it := db.bIterator(srckeys[srcIdx])
|
||||
for ; it.Valid(); it.Next() {
|
||||
if _, seq, err = db.bDecodeBinKey(it.RawKey()); err != nil {
|
||||
// to do ...
|
||||
it.Close()
|
||||
return
|
||||
}
|
||||
segments[seq] = it.Value()
|
||||
}
|
||||
it.Close()
|
||||
srcIdx++
|
||||
}
|
||||
|
||||
// operation with following keys
|
||||
var res []byte
|
||||
for i := srcIdx; i < keyNum; i++ {
|
||||
if srckeys[i] == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
it := db.bIterator(srckeys[i])
|
||||
for idx, end := uint32(0), false; !end; it.Next() {
|
||||
end = !it.Valid()
|
||||
if !end {
|
||||
if _, seq, err = db.bDecodeBinKey(it.RawKey()); err != nil {
|
||||
// to do ...
|
||||
it.Close()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
seq = maxDstSeq + 1
|
||||
}
|
||||
|
||||
// todo :
|
||||
// operation 'and' can be optimize here :
|
||||
// if seq > max_segments_idx, this loop can be break,
|
||||
// which can avoid cost from Key() and bDecodeBinKey()
|
||||
|
||||
for ; idx < seq; idx++ {
|
||||
res = nil
|
||||
exeOp(segments[idx], nil, &res)
|
||||
segments[idx] = res
|
||||
}
|
||||
|
||||
if !end {
|
||||
res = it.Value()
|
||||
exeOp(segments[seq], res, &res)
|
||||
segments[seq] = res
|
||||
idx++
|
||||
}
|
||||
}
|
||||
it.Close()
|
||||
}
|
||||
|
||||
// clear the old data in case
|
||||
db.bDelete(t, dstkey)
|
||||
db.rmExpire(t, BitType, dstkey)
|
||||
|
||||
// set data
|
||||
db.bSetMeta(t, dstkey, maxDstSeq, maxDstOff)
|
||||
|
||||
var bk []byte
|
||||
for seq, segt := range segments {
|
||||
if segt != nil {
|
||||
bk = db.bEncodeBinKey(dstkey, uint32(seq))
|
||||
t.Put(bk, segt)
|
||||
}
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
if err == nil {
|
||||
// blen = int32(db.bCapByteSize(maxDstOff, maxDstOff))
|
||||
blen = int32(maxDstSeq<<segBitWidth | maxDstOff + 1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) BExpire(key []byte, duration int64) (int64, error) {
|
||||
if duration <= 0 {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return db.bExpireAt(key, time.Now().Unix()+duration)
|
||||
}
|
||||
|
||||
func (db *DB) BExpireAt(key []byte, when int64) (int64, error) {
|
||||
if when <= time.Now().Unix() {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return db.bExpireAt(key, when)
|
||||
}
|
||||
|
||||
func (db *DB) BTTL(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return db.ttl(BitType, key)
|
||||
}
|
||||
|
||||
func (db *DB) BPersist(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.binBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
n, err := db.rmExpire(t, BitType, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) BScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
|
||||
return db.scan(BitMetaType, key, count, inclusive, match)
|
||||
}
|
||||
|
||||
func (db *DB) bFlush() (drop int64, err error) {
|
||||
t := db.binBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return db.flushType(t, BitType)
|
||||
}
|
509
vendor/github.com/lunny/nodb/t_hash.go
generated
vendored
Normal file
509
vendor/github.com/lunny/nodb/t_hash.go
generated
vendored
Normal file
@ -0,0 +1,509 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
type FVPair struct {
|
||||
Field []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
var errHashKey = errors.New("invalid hash key")
|
||||
var errHSizeKey = errors.New("invalid hsize key")
|
||||
|
||||
const (
|
||||
hashStartSep byte = ':'
|
||||
hashStopSep byte = hashStartSep + 1
|
||||
)
|
||||
|
||||
func checkHashKFSize(key []byte, field []byte) error {
|
||||
if len(key) > MaxKeySize || len(key) == 0 {
|
||||
return errKeySize
|
||||
} else if len(field) > MaxHashFieldSize || len(field) == 0 {
|
||||
return errHashFieldSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) hEncodeSizeKey(key []byte) []byte {
|
||||
buf := make([]byte, len(key)+2)
|
||||
|
||||
buf[0] = db.index
|
||||
buf[1] = HSizeType
|
||||
|
||||
copy(buf[2:], key)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) hDecodeSizeKey(ek []byte) ([]byte, error) {
|
||||
if len(ek) < 2 || ek[0] != db.index || ek[1] != HSizeType {
|
||||
return nil, errHSizeKey
|
||||
}
|
||||
|
||||
return ek[2:], nil
|
||||
}
|
||||
|
||||
func (db *DB) hEncodeHashKey(key []byte, field []byte) []byte {
|
||||
buf := make([]byte, len(key)+len(field)+1+1+2+1)
|
||||
|
||||
pos := 0
|
||||
buf[pos] = db.index
|
||||
pos++
|
||||
buf[pos] = HashType
|
||||
pos++
|
||||
|
||||
binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
|
||||
pos += 2
|
||||
|
||||
copy(buf[pos:], key)
|
||||
pos += len(key)
|
||||
|
||||
buf[pos] = hashStartSep
|
||||
pos++
|
||||
copy(buf[pos:], field)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) hDecodeHashKey(ek []byte) ([]byte, []byte, error) {
|
||||
if len(ek) < 5 || ek[0] != db.index || ek[1] != HashType {
|
||||
return nil, nil, errHashKey
|
||||
}
|
||||
|
||||
pos := 2
|
||||
keyLen := int(binary.BigEndian.Uint16(ek[pos:]))
|
||||
pos += 2
|
||||
|
||||
if keyLen+5 > len(ek) {
|
||||
return nil, nil, errHashKey
|
||||
}
|
||||
|
||||
key := ek[pos : pos+keyLen]
|
||||
pos += keyLen
|
||||
|
||||
if ek[pos] != hashStartSep {
|
||||
return nil, nil, errHashKey
|
||||
}
|
||||
|
||||
pos++
|
||||
field := ek[pos:]
|
||||
return key, field, nil
|
||||
}
|
||||
|
||||
func (db *DB) hEncodeStartKey(key []byte) []byte {
|
||||
return db.hEncodeHashKey(key, nil)
|
||||
}
|
||||
|
||||
func (db *DB) hEncodeStopKey(key []byte) []byte {
|
||||
k := db.hEncodeHashKey(key, nil)
|
||||
|
||||
k[len(k)-1] = hashStopSep
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
func (db *DB) hSetItem(key []byte, field []byte, value []byte) (int64, error) {
|
||||
t := db.hashBatch
|
||||
|
||||
ek := db.hEncodeHashKey(key, field)
|
||||
|
||||
var n int64 = 1
|
||||
if v, _ := db.bucket.Get(ek); v != nil {
|
||||
n = 0
|
||||
} else {
|
||||
if _, err := db.hIncrSize(key, 1); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
t.Put(ek, value)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// ps : here just focus on deleting the hash data,
|
||||
// any other likes expire is ignore.
|
||||
func (db *DB) hDelete(t *batch, key []byte) int64 {
|
||||
sk := db.hEncodeSizeKey(key)
|
||||
start := db.hEncodeStartKey(key)
|
||||
stop := db.hEncodeStopKey(key)
|
||||
|
||||
var num int64 = 0
|
||||
it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
|
||||
for ; it.Valid(); it.Next() {
|
||||
t.Delete(it.Key())
|
||||
num++
|
||||
}
|
||||
it.Close()
|
||||
|
||||
t.Delete(sk)
|
||||
return num
|
||||
}
|
||||
|
||||
func (db *DB) hExpireAt(key []byte, when int64) (int64, error) {
|
||||
t := db.hashBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if hlen, err := db.HLen(key); err != nil || hlen == 0 {
|
||||
return 0, err
|
||||
} else {
|
||||
db.expireAt(t, HashType, key, when)
|
||||
if err := t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (db *DB) HLen(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return Int64(db.bucket.Get(db.hEncodeSizeKey(key)))
|
||||
}
|
||||
|
||||
func (db *DB) HSet(key []byte, field []byte, value []byte) (int64, error) {
|
||||
if err := checkHashKFSize(key, field); err != nil {
|
||||
return 0, err
|
||||
} else if err := checkValueSize(value); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.hashBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
n, err := db.hSetItem(key, field, value)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//todo add binlog
|
||||
|
||||
err = t.Commit()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) HGet(key []byte, field []byte) ([]byte, error) {
|
||||
if err := checkHashKFSize(key, field); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db.bucket.Get(db.hEncodeHashKey(key, field))
|
||||
}
|
||||
|
||||
func (db *DB) HMset(key []byte, args ...FVPair) error {
|
||||
t := db.hashBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var err error
|
||||
var ek []byte
|
||||
var num int64 = 0
|
||||
for i := 0; i < len(args); i++ {
|
||||
if err := checkHashKFSize(key, args[i].Field); err != nil {
|
||||
return err
|
||||
} else if err := checkValueSize(args[i].Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ek = db.hEncodeHashKey(key, args[i].Field)
|
||||
|
||||
if v, err := db.bucket.Get(ek); err != nil {
|
||||
return err
|
||||
} else if v == nil {
|
||||
num++
|
||||
}
|
||||
|
||||
t.Put(ek, args[i].Value)
|
||||
}
|
||||
|
||||
if _, err = db.hIncrSize(key, num); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//todo add binglog
|
||||
err = t.Commit()
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) HMget(key []byte, args ...[]byte) ([][]byte, error) {
|
||||
var ek []byte
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
defer it.Close()
|
||||
|
||||
r := make([][]byte, len(args))
|
||||
for i := 0; i < len(args); i++ {
|
||||
if err := checkHashKFSize(key, args[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ek = db.hEncodeHashKey(key, args[i])
|
||||
|
||||
r[i] = it.Find(ek)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (db *DB) HDel(key []byte, args ...[]byte) (int64, error) {
|
||||
t := db.hashBatch
|
||||
|
||||
var ek []byte
|
||||
var v []byte
|
||||
var err error
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
defer it.Close()
|
||||
|
||||
var num int64 = 0
|
||||
for i := 0; i < len(args); i++ {
|
||||
if err := checkHashKFSize(key, args[i]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ek = db.hEncodeHashKey(key, args[i])
|
||||
|
||||
v = it.RawFind(ek)
|
||||
if v == nil {
|
||||
continue
|
||||
} else {
|
||||
num++
|
||||
t.Delete(ek)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = db.hIncrSize(key, -num); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (db *DB) hIncrSize(key []byte, delta int64) (int64, error) {
|
||||
t := db.hashBatch
|
||||
sk := db.hEncodeSizeKey(key)
|
||||
|
||||
var err error
|
||||
var size int64 = 0
|
||||
if size, err = Int64(db.bucket.Get(sk)); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
size += delta
|
||||
if size <= 0 {
|
||||
size = 0
|
||||
t.Delete(sk)
|
||||
db.rmExpire(t, HashType, key)
|
||||
} else {
|
||||
t.Put(sk, PutInt64(size))
|
||||
}
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (db *DB) HIncrBy(key []byte, field []byte, delta int64) (int64, error) {
|
||||
if err := checkHashKFSize(key, field); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.hashBatch
|
||||
var ek []byte
|
||||
var err error
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
ek = db.hEncodeHashKey(key, field)
|
||||
|
||||
var n int64 = 0
|
||||
if n, err = StrInt64(db.bucket.Get(ek)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n += delta
|
||||
|
||||
_, err = db.hSetItem(key, field, StrPutInt64(n))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) HGetAll(key []byte) ([]FVPair, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
start := db.hEncodeStartKey(key)
|
||||
stop := db.hEncodeStopKey(key)
|
||||
|
||||
v := make([]FVPair, 0, 16)
|
||||
|
||||
it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
|
||||
for ; it.Valid(); it.Next() {
|
||||
_, f, err := db.hDecodeHashKey(it.Key())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v = append(v, FVPair{Field: f, Value: it.Value()})
|
||||
}
|
||||
|
||||
it.Close()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) HKeys(key []byte) ([][]byte, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
start := db.hEncodeStartKey(key)
|
||||
stop := db.hEncodeStopKey(key)
|
||||
|
||||
v := make([][]byte, 0, 16)
|
||||
|
||||
it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
|
||||
for ; it.Valid(); it.Next() {
|
||||
_, f, err := db.hDecodeHashKey(it.Key())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v = append(v, f)
|
||||
}
|
||||
|
||||
it.Close()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) HValues(key []byte) ([][]byte, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
start := db.hEncodeStartKey(key)
|
||||
stop := db.hEncodeStopKey(key)
|
||||
|
||||
v := make([][]byte, 0, 16)
|
||||
|
||||
it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
|
||||
for ; it.Valid(); it.Next() {
|
||||
_, _, err := db.hDecodeHashKey(it.Key())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v = append(v, it.Value())
|
||||
}
|
||||
|
||||
it.Close()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) HClear(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.hashBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
num := db.hDelete(t, key)
|
||||
db.rmExpire(t, HashType, key)
|
||||
|
||||
err := t.Commit()
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (db *DB) HMclear(keys ...[]byte) (int64, error) {
|
||||
t := db.hashBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for _, key := range keys {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
db.hDelete(t, key)
|
||||
db.rmExpire(t, HashType, key)
|
||||
}
|
||||
|
||||
err := t.Commit()
|
||||
return int64(len(keys)), err
|
||||
}
|
||||
|
||||
func (db *DB) hFlush() (drop int64, err error) {
|
||||
t := db.hashBatch
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return db.flushType(t, HashType)
|
||||
}
|
||||
|
||||
func (db *DB) HScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
|
||||
return db.scan(HSizeType, key, count, inclusive, match)
|
||||
}
|
||||
|
||||
func (db *DB) HExpire(key []byte, duration int64) (int64, error) {
|
||||
if duration <= 0 {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.hExpireAt(key, time.Now().Unix()+duration)
|
||||
}
|
||||
|
||||
func (db *DB) HExpireAt(key []byte, when int64) (int64, error) {
|
||||
if when <= time.Now().Unix() {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.hExpireAt(key, when)
|
||||
}
|
||||
|
||||
func (db *DB) HTTL(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return db.ttl(HashType, key)
|
||||
}
|
||||
|
||||
func (db *DB) HPersist(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.hashBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
n, err := db.rmExpire(t, HashType, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return n, err
|
||||
}
|
387
vendor/github.com/lunny/nodb/t_kv.go
generated
vendored
Normal file
387
vendor/github.com/lunny/nodb/t_kv.go
generated
vendored
Normal file
@ -0,0 +1,387 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KVPair struct {
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
var errKVKey = errors.New("invalid encode kv key")
|
||||
|
||||
func checkKeySize(key []byte) error {
|
||||
if len(key) > MaxKeySize || len(key) == 0 {
|
||||
return errKeySize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkValueSize(value []byte) error {
|
||||
if len(value) > MaxValueSize {
|
||||
return errValueSize
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) encodeKVKey(key []byte) []byte {
|
||||
ek := make([]byte, len(key)+2)
|
||||
ek[0] = db.index
|
||||
ek[1] = KVType
|
||||
copy(ek[2:], key)
|
||||
return ek
|
||||
}
|
||||
|
||||
func (db *DB) decodeKVKey(ek []byte) ([]byte, error) {
|
||||
if len(ek) < 2 || ek[0] != db.index || ek[1] != KVType {
|
||||
return nil, errKVKey
|
||||
}
|
||||
|
||||
return ek[2:], nil
|
||||
}
|
||||
|
||||
func (db *DB) encodeKVMinKey() []byte {
|
||||
ek := db.encodeKVKey(nil)
|
||||
return ek
|
||||
}
|
||||
|
||||
func (db *DB) encodeKVMaxKey() []byte {
|
||||
ek := db.encodeKVKey(nil)
|
||||
ek[len(ek)-1] = KVType + 1
|
||||
return ek
|
||||
}
|
||||
|
||||
func (db *DB) incr(key []byte, delta int64) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var err error
|
||||
key = db.encodeKVKey(key)
|
||||
|
||||
t := db.kvBatch
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var n int64
|
||||
n, err = StrInt64(db.bucket.Get(key))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
n += delta
|
||||
|
||||
t.Put(key, StrPutInt64(n))
|
||||
|
||||
//todo binlog
|
||||
|
||||
err = t.Commit()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// ps : here just focus on deleting the key-value data,
|
||||
// any other likes expire is ignore.
|
||||
func (db *DB) delete(t *batch, key []byte) int64 {
|
||||
key = db.encodeKVKey(key)
|
||||
t.Delete(key)
|
||||
return 1
|
||||
}
|
||||
|
||||
func (db *DB) setExpireAt(key []byte, when int64) (int64, error) {
|
||||
t := db.kvBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if exist, err := db.Exists(key); err != nil || exist == 0 {
|
||||
return 0, err
|
||||
} else {
|
||||
db.expireAt(t, KVType, key, when)
|
||||
if err := t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (db *DB) Decr(key []byte) (int64, error) {
|
||||
return db.incr(key, -1)
|
||||
}
|
||||
|
||||
func (db *DB) DecrBy(key []byte, decrement int64) (int64, error) {
|
||||
return db.incr(key, -decrement)
|
||||
}
|
||||
|
||||
func (db *DB) Del(keys ...[]byte) (int64, error) {
|
||||
if len(keys) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
codedKeys := make([][]byte, len(keys))
|
||||
for i, k := range keys {
|
||||
codedKeys[i] = db.encodeKVKey(k)
|
||||
}
|
||||
|
||||
t := db.kvBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for i, k := range keys {
|
||||
t.Delete(codedKeys[i])
|
||||
db.rmExpire(t, KVType, k)
|
||||
}
|
||||
|
||||
err := t.Commit()
|
||||
return int64(len(keys)), err
|
||||
}
|
||||
|
||||
func (db *DB) Exists(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var err error
|
||||
key = db.encodeKVKey(key)
|
||||
|
||||
var v []byte
|
||||
v, err = db.bucket.Get(key)
|
||||
if v != nil && err == nil {
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func (db *DB) Get(key []byte) ([]byte, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key = db.encodeKVKey(key)
|
||||
|
||||
return db.bucket.Get(key)
|
||||
}
|
||||
|
||||
func (db *DB) GetSet(key []byte, value []byte) ([]byte, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
} else if err := checkValueSize(value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key = db.encodeKVKey(key)
|
||||
|
||||
t := db.kvBatch
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
oldValue, err := db.bucket.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Put(key, value)
|
||||
//todo, binlog
|
||||
|
||||
err = t.Commit()
|
||||
|
||||
return oldValue, err
|
||||
}
|
||||
|
||||
func (db *DB) Incr(key []byte) (int64, error) {
|
||||
return db.incr(key, 1)
|
||||
}
|
||||
|
||||
func (db *DB) IncrBy(key []byte, increment int64) (int64, error) {
|
||||
return db.incr(key, increment)
|
||||
}
|
||||
|
||||
func (db *DB) MGet(keys ...[]byte) ([][]byte, error) {
|
||||
values := make([][]byte, len(keys))
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
defer it.Close()
|
||||
|
||||
for i := range keys {
|
||||
if err := checkKeySize(keys[i]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values[i] = it.Find(db.encodeKVKey(keys[i]))
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (db *DB) MSet(args ...KVPair) error {
|
||||
if len(args) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
t := db.kvBatch
|
||||
|
||||
var err error
|
||||
var key []byte
|
||||
var value []byte
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for i := 0; i < len(args); i++ {
|
||||
if err := checkKeySize(args[i].Key); err != nil {
|
||||
return err
|
||||
} else if err := checkValueSize(args[i].Value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key = db.encodeKVKey(args[i].Key)
|
||||
|
||||
value = args[i].Value
|
||||
|
||||
t.Put(key, value)
|
||||
|
||||
//todo binlog
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) Set(key []byte, value []byte) error {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return err
|
||||
} else if err := checkValueSize(value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var err error
|
||||
key = db.encodeKVKey(key)
|
||||
|
||||
t := db.kvBatch
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.Put(key, value)
|
||||
|
||||
err = t.Commit()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) SetNX(key []byte, value []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
} else if err := checkValueSize(value); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var err error
|
||||
key = db.encodeKVKey(key)
|
||||
|
||||
var n int64 = 1
|
||||
|
||||
t := db.kvBatch
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if v, err := db.bucket.Get(key); err != nil {
|
||||
return 0, err
|
||||
} else if v != nil {
|
||||
n = 0
|
||||
} else {
|
||||
t.Put(key, value)
|
||||
|
||||
//todo binlog
|
||||
|
||||
err = t.Commit()
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) flush() (drop int64, err error) {
|
||||
t := db.kvBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
return db.flushType(t, KVType)
|
||||
}
|
||||
|
||||
//if inclusive is true, scan range [key, inf) else (key, inf)
|
||||
func (db *DB) Scan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
|
||||
return db.scan(KVType, key, count, inclusive, match)
|
||||
}
|
||||
|
||||
func (db *DB) Expire(key []byte, duration int64) (int64, error) {
|
||||
if duration <= 0 {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.setExpireAt(key, time.Now().Unix()+duration)
|
||||
}
|
||||
|
||||
func (db *DB) ExpireAt(key []byte, when int64) (int64, error) {
|
||||
if when <= time.Now().Unix() {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.setExpireAt(key, when)
|
||||
}
|
||||
|
||||
func (db *DB) TTL(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return db.ttl(KVType, key)
|
||||
}
|
||||
|
||||
func (db *DB) Persist(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.kvBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
n, err := db.rmExpire(t, KVType, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) Lock() {
|
||||
t := db.kvBatch
|
||||
t.Lock()
|
||||
}
|
||||
|
||||
func (db *DB) Remove(key []byte) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
t := db.kvBatch
|
||||
t.Delete(db.encodeKVKey(key))
|
||||
_, err := db.rmExpire(t, KVType, key)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (db *DB) Commit() error {
|
||||
t := db.kvBatch
|
||||
return t.Commit()
|
||||
}
|
||||
|
||||
func (db *DB) Unlock() {
|
||||
t := db.kvBatch
|
||||
t.Unlock()
|
||||
}
|
492
vendor/github.com/lunny/nodb/t_list.go
generated
vendored
Normal file
492
vendor/github.com/lunny/nodb/t_list.go
generated
vendored
Normal file
@ -0,0 +1,492 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
const (
|
||||
listHeadSeq int32 = 1
|
||||
listTailSeq int32 = 2
|
||||
|
||||
listMinSeq int32 = 1000
|
||||
listMaxSeq int32 = 1<<31 - 1000
|
||||
listInitialSeq int32 = listMinSeq + (listMaxSeq-listMinSeq)/2
|
||||
)
|
||||
|
||||
var errLMetaKey = errors.New("invalid lmeta key")
|
||||
var errListKey = errors.New("invalid list key")
|
||||
var errListSeq = errors.New("invalid list sequence, overflow")
|
||||
|
||||
func (db *DB) lEncodeMetaKey(key []byte) []byte {
|
||||
buf := make([]byte, len(key)+2)
|
||||
buf[0] = db.index
|
||||
buf[1] = LMetaType
|
||||
|
||||
copy(buf[2:], key)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) lDecodeMetaKey(ek []byte) ([]byte, error) {
|
||||
if len(ek) < 2 || ek[0] != db.index || ek[1] != LMetaType {
|
||||
return nil, errLMetaKey
|
||||
}
|
||||
|
||||
return ek[2:], nil
|
||||
}
|
||||
|
||||
func (db *DB) lEncodeListKey(key []byte, seq int32) []byte {
|
||||
buf := make([]byte, len(key)+8)
|
||||
|
||||
pos := 0
|
||||
buf[pos] = db.index
|
||||
pos++
|
||||
buf[pos] = ListType
|
||||
pos++
|
||||
|
||||
binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
|
||||
pos += 2
|
||||
|
||||
copy(buf[pos:], key)
|
||||
pos += len(key)
|
||||
|
||||
binary.BigEndian.PutUint32(buf[pos:], uint32(seq))
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) lDecodeListKey(ek []byte) (key []byte, seq int32, err error) {
|
||||
if len(ek) < 8 || ek[0] != db.index || ek[1] != ListType {
|
||||
err = errListKey
|
||||
return
|
||||
}
|
||||
|
||||
keyLen := int(binary.BigEndian.Uint16(ek[2:]))
|
||||
if keyLen+8 != len(ek) {
|
||||
err = errListKey
|
||||
return
|
||||
}
|
||||
|
||||
key = ek[4 : 4+keyLen]
|
||||
seq = int32(binary.BigEndian.Uint32(ek[4+keyLen:]))
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) lpush(key []byte, whereSeq int32, args ...[]byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var headSeq int32
|
||||
var tailSeq int32
|
||||
var size int32
|
||||
var err error
|
||||
|
||||
t := db.listBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
metaKey := db.lEncodeMetaKey(key)
|
||||
headSeq, tailSeq, size, err = db.lGetMeta(nil, metaKey)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var pushCnt int = len(args)
|
||||
if pushCnt == 0 {
|
||||
return int64(size), nil
|
||||
}
|
||||
|
||||
var seq int32 = headSeq
|
||||
var delta int32 = -1
|
||||
if whereSeq == listTailSeq {
|
||||
seq = tailSeq
|
||||
delta = 1
|
||||
}
|
||||
|
||||
// append elements
|
||||
if size > 0 {
|
||||
seq += delta
|
||||
}
|
||||
|
||||
for i := 0; i < pushCnt; i++ {
|
||||
ek := db.lEncodeListKey(key, seq+int32(i)*delta)
|
||||
t.Put(ek, args[i])
|
||||
}
|
||||
|
||||
seq += int32(pushCnt-1) * delta
|
||||
if seq <= listMinSeq || seq >= listMaxSeq {
|
||||
return 0, errListSeq
|
||||
}
|
||||
|
||||
// set meta info
|
||||
if whereSeq == listHeadSeq {
|
||||
headSeq = seq
|
||||
} else {
|
||||
tailSeq = seq
|
||||
}
|
||||
|
||||
db.lSetMeta(metaKey, headSeq, tailSeq)
|
||||
|
||||
err = t.Commit()
|
||||
return int64(size) + int64(pushCnt), err
|
||||
}
|
||||
|
||||
func (db *DB) lpop(key []byte, whereSeq int32) ([]byte, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := db.listBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var headSeq int32
|
||||
var tailSeq int32
|
||||
var err error
|
||||
|
||||
metaKey := db.lEncodeMetaKey(key)
|
||||
headSeq, tailSeq, _, err = db.lGetMeta(nil, metaKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var value []byte
|
||||
|
||||
var seq int32 = headSeq
|
||||
if whereSeq == listTailSeq {
|
||||
seq = tailSeq
|
||||
}
|
||||
|
||||
itemKey := db.lEncodeListKey(key, seq)
|
||||
value, err = db.bucket.Get(itemKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if whereSeq == listHeadSeq {
|
||||
headSeq += 1
|
||||
} else {
|
||||
tailSeq -= 1
|
||||
}
|
||||
|
||||
t.Delete(itemKey)
|
||||
size := db.lSetMeta(metaKey, headSeq, tailSeq)
|
||||
if size == 0 {
|
||||
db.rmExpire(t, HashType, key)
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return value, err
|
||||
}
|
||||
|
||||
// ps : here just focus on deleting the list data,
|
||||
// any other likes expire is ignore.
|
||||
func (db *DB) lDelete(t *batch, key []byte) int64 {
|
||||
mk := db.lEncodeMetaKey(key)
|
||||
|
||||
var headSeq int32
|
||||
var tailSeq int32
|
||||
var err error
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
defer it.Close()
|
||||
|
||||
headSeq, tailSeq, _, err = db.lGetMeta(it, mk)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var num int64 = 0
|
||||
startKey := db.lEncodeListKey(key, headSeq)
|
||||
stopKey := db.lEncodeListKey(key, tailSeq)
|
||||
|
||||
rit := store.NewRangeIterator(it, &store.Range{startKey, stopKey, store.RangeClose})
|
||||
for ; rit.Valid(); rit.Next() {
|
||||
t.Delete(rit.RawKey())
|
||||
num++
|
||||
}
|
||||
|
||||
t.Delete(mk)
|
||||
|
||||
return num
|
||||
}
|
||||
|
||||
func (db *DB) lGetMeta(it *store.Iterator, ek []byte) (headSeq int32, tailSeq int32, size int32, err error) {
|
||||
var v []byte
|
||||
if it != nil {
|
||||
v = it.Find(ek)
|
||||
} else {
|
||||
v, err = db.bucket.Get(ek)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
} else if v == nil {
|
||||
headSeq = listInitialSeq
|
||||
tailSeq = listInitialSeq
|
||||
size = 0
|
||||
return
|
||||
} else {
|
||||
headSeq = int32(binary.LittleEndian.Uint32(v[0:4]))
|
||||
tailSeq = int32(binary.LittleEndian.Uint32(v[4:8]))
|
||||
size = tailSeq - headSeq + 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) lSetMeta(ek []byte, headSeq int32, tailSeq int32) int32 {
|
||||
t := db.listBatch
|
||||
|
||||
var size int32 = tailSeq - headSeq + 1
|
||||
if size < 0 {
|
||||
// todo : log error + panic
|
||||
} else if size == 0 {
|
||||
t.Delete(ek)
|
||||
} else {
|
||||
buf := make([]byte, 8)
|
||||
|
||||
binary.LittleEndian.PutUint32(buf[0:4], uint32(headSeq))
|
||||
binary.LittleEndian.PutUint32(buf[4:8], uint32(tailSeq))
|
||||
|
||||
t.Put(ek, buf)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
func (db *DB) lExpireAt(key []byte, when int64) (int64, error) {
|
||||
t := db.listBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if llen, err := db.LLen(key); err != nil || llen == 0 {
|
||||
return 0, err
|
||||
} else {
|
||||
db.expireAt(t, ListType, key, when)
|
||||
if err := t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (db *DB) LIndex(key []byte, index int32) ([]byte, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var seq int32
|
||||
var headSeq int32
|
||||
var tailSeq int32
|
||||
var err error
|
||||
|
||||
metaKey := db.lEncodeMetaKey(key)
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
defer it.Close()
|
||||
|
||||
headSeq, tailSeq, _, err = db.lGetMeta(it, metaKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if index >= 0 {
|
||||
seq = headSeq + index
|
||||
} else {
|
||||
seq = tailSeq + index + 1
|
||||
}
|
||||
|
||||
sk := db.lEncodeListKey(key, seq)
|
||||
v := it.Find(sk)
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) LLen(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ek := db.lEncodeMetaKey(key)
|
||||
_, _, size, err := db.lGetMeta(nil, ek)
|
||||
return int64(size), err
|
||||
}
|
||||
|
||||
func (db *DB) LPop(key []byte) ([]byte, error) {
|
||||
return db.lpop(key, listHeadSeq)
|
||||
}
|
||||
|
||||
func (db *DB) LPush(key []byte, arg1 []byte, args ...[]byte) (int64, error) {
|
||||
var argss = [][]byte{arg1}
|
||||
argss = append(argss, args...)
|
||||
return db.lpush(key, listHeadSeq, argss...)
|
||||
}
|
||||
|
||||
func (db *DB) LRange(key []byte, start int32, stop int32) ([][]byte, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var headSeq int32
|
||||
var llen int32
|
||||
var err error
|
||||
|
||||
metaKey := db.lEncodeMetaKey(key)
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
defer it.Close()
|
||||
|
||||
if headSeq, _, llen, err = db.lGetMeta(it, metaKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if start < 0 {
|
||||
start = llen + start
|
||||
}
|
||||
if stop < 0 {
|
||||
stop = llen + stop
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
|
||||
if start > stop || start >= llen {
|
||||
return [][]byte{}, nil
|
||||
}
|
||||
|
||||
if stop >= llen {
|
||||
stop = llen - 1
|
||||
}
|
||||
|
||||
limit := (stop - start) + 1
|
||||
headSeq += start
|
||||
|
||||
v := make([][]byte, 0, limit)
|
||||
|
||||
startKey := db.lEncodeListKey(key, headSeq)
|
||||
rit := store.NewRangeLimitIterator(it,
|
||||
&store.Range{
|
||||
Min: startKey,
|
||||
Max: nil,
|
||||
Type: store.RangeClose},
|
||||
&store.Limit{
|
||||
Offset: 0,
|
||||
Count: int(limit)})
|
||||
|
||||
for ; rit.Valid(); rit.Next() {
|
||||
v = append(v, rit.Value())
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) RPop(key []byte) ([]byte, error) {
|
||||
return db.lpop(key, listTailSeq)
|
||||
}
|
||||
|
||||
func (db *DB) RPush(key []byte, arg1 []byte, args ...[]byte) (int64, error) {
|
||||
var argss = [][]byte{arg1}
|
||||
argss = append(argss, args...)
|
||||
return db.lpush(key, listTailSeq, argss...)
|
||||
}
|
||||
|
||||
func (db *DB) LClear(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.listBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
num := db.lDelete(t, key)
|
||||
db.rmExpire(t, ListType, key)
|
||||
|
||||
err := t.Commit()
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (db *DB) LMclear(keys ...[]byte) (int64, error) {
|
||||
t := db.listBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for _, key := range keys {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
db.lDelete(t, key)
|
||||
db.rmExpire(t, ListType, key)
|
||||
|
||||
}
|
||||
|
||||
err := t.Commit()
|
||||
return int64(len(keys)), err
|
||||
}
|
||||
|
||||
func (db *DB) lFlush() (drop int64, err error) {
|
||||
t := db.listBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
return db.flushType(t, ListType)
|
||||
}
|
||||
|
||||
func (db *DB) LExpire(key []byte, duration int64) (int64, error) {
|
||||
if duration <= 0 {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.lExpireAt(key, time.Now().Unix()+duration)
|
||||
}
|
||||
|
||||
func (db *DB) LExpireAt(key []byte, when int64) (int64, error) {
|
||||
if when <= time.Now().Unix() {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.lExpireAt(key, when)
|
||||
}
|
||||
|
||||
func (db *DB) LTTL(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return db.ttl(ListType, key)
|
||||
}
|
||||
|
||||
func (db *DB) LPersist(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.listBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
n, err := db.rmExpire(t, ListType, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) LScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
|
||||
return db.scan(LMetaType, key, count, inclusive, match)
|
||||
}
|
||||
|
||||
func (db *DB) lEncodeMinKey() []byte {
|
||||
return db.lEncodeMetaKey(nil)
|
||||
}
|
||||
|
||||
func (db *DB) lEncodeMaxKey() []byte {
|
||||
ek := db.lEncodeMetaKey(nil)
|
||||
ek[len(ek)-1] = LMetaType + 1
|
||||
return ek
|
||||
}
|
601
vendor/github.com/lunny/nodb/t_set.go
generated
vendored
Normal file
601
vendor/github.com/lunny/nodb/t_set.go
generated
vendored
Normal file
@ -0,0 +1,601 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
var errSetKey = errors.New("invalid set key")
|
||||
var errSSizeKey = errors.New("invalid ssize key")
|
||||
|
||||
const (
|
||||
setStartSep byte = ':'
|
||||
setStopSep byte = setStartSep + 1
|
||||
UnionType byte = 51
|
||||
DiffType byte = 52
|
||||
InterType byte = 53
|
||||
)
|
||||
|
||||
func checkSetKMSize(key []byte, member []byte) error {
|
||||
if len(key) > MaxKeySize || len(key) == 0 {
|
||||
return errKeySize
|
||||
} else if len(member) > MaxSetMemberSize || len(member) == 0 {
|
||||
return errSetMemberSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) sEncodeSizeKey(key []byte) []byte {
|
||||
buf := make([]byte, len(key)+2)
|
||||
|
||||
buf[0] = db.index
|
||||
buf[1] = SSizeType
|
||||
|
||||
copy(buf[2:], key)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) sDecodeSizeKey(ek []byte) ([]byte, error) {
|
||||
if len(ek) < 2 || ek[0] != db.index || ek[1] != SSizeType {
|
||||
return nil, errSSizeKey
|
||||
}
|
||||
|
||||
return ek[2:], nil
|
||||
}
|
||||
|
||||
func (db *DB) sEncodeSetKey(key []byte, member []byte) []byte {
|
||||
buf := make([]byte, len(key)+len(member)+1+1+2+1)
|
||||
|
||||
pos := 0
|
||||
buf[pos] = db.index
|
||||
pos++
|
||||
buf[pos] = SetType
|
||||
pos++
|
||||
|
||||
binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
|
||||
pos += 2
|
||||
|
||||
copy(buf[pos:], key)
|
||||
pos += len(key)
|
||||
|
||||
buf[pos] = setStartSep
|
||||
pos++
|
||||
copy(buf[pos:], member)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) sDecodeSetKey(ek []byte) ([]byte, []byte, error) {
|
||||
if len(ek) < 5 || ek[0] != db.index || ek[1] != SetType {
|
||||
return nil, nil, errSetKey
|
||||
}
|
||||
|
||||
pos := 2
|
||||
keyLen := int(binary.BigEndian.Uint16(ek[pos:]))
|
||||
pos += 2
|
||||
|
||||
if keyLen+5 > len(ek) {
|
||||
return nil, nil, errSetKey
|
||||
}
|
||||
|
||||
key := ek[pos : pos+keyLen]
|
||||
pos += keyLen
|
||||
|
||||
if ek[pos] != hashStartSep {
|
||||
return nil, nil, errSetKey
|
||||
}
|
||||
|
||||
pos++
|
||||
member := ek[pos:]
|
||||
return key, member, nil
|
||||
}
|
||||
|
||||
func (db *DB) sEncodeStartKey(key []byte) []byte {
|
||||
return db.sEncodeSetKey(key, nil)
|
||||
}
|
||||
|
||||
func (db *DB) sEncodeStopKey(key []byte) []byte {
|
||||
k := db.sEncodeSetKey(key, nil)
|
||||
|
||||
k[len(k)-1] = setStopSep
|
||||
|
||||
return k
|
||||
}
|
||||
|
||||
func (db *DB) sFlush() (drop int64, err error) {
|
||||
|
||||
t := db.setBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return db.flushType(t, SetType)
|
||||
}
|
||||
|
||||
func (db *DB) sDelete(t *batch, key []byte) int64 {
|
||||
sk := db.sEncodeSizeKey(key)
|
||||
start := db.sEncodeStartKey(key)
|
||||
stop := db.sEncodeStopKey(key)
|
||||
|
||||
var num int64 = 0
|
||||
it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
|
||||
for ; it.Valid(); it.Next() {
|
||||
t.Delete(it.RawKey())
|
||||
num++
|
||||
}
|
||||
|
||||
it.Close()
|
||||
|
||||
t.Delete(sk)
|
||||
return num
|
||||
}
|
||||
|
||||
func (db *DB) sIncrSize(key []byte, delta int64) (int64, error) {
|
||||
t := db.setBatch
|
||||
sk := db.sEncodeSizeKey(key)
|
||||
|
||||
var err error
|
||||
var size int64 = 0
|
||||
if size, err = Int64(db.bucket.Get(sk)); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
size += delta
|
||||
if size <= 0 {
|
||||
size = 0
|
||||
t.Delete(sk)
|
||||
db.rmExpire(t, SetType, key)
|
||||
} else {
|
||||
t.Put(sk, PutInt64(size))
|
||||
}
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (db *DB) sExpireAt(key []byte, when int64) (int64, error) {
|
||||
t := db.setBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if scnt, err := db.SCard(key); err != nil || scnt == 0 {
|
||||
return 0, err
|
||||
} else {
|
||||
db.expireAt(t, SetType, key, when)
|
||||
if err := t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (db *DB) sSetItem(key []byte, member []byte) (int64, error) {
|
||||
t := db.setBatch
|
||||
ek := db.sEncodeSetKey(key, member)
|
||||
|
||||
var n int64 = 1
|
||||
if v, _ := db.bucket.Get(ek); v != nil {
|
||||
n = 0
|
||||
} else {
|
||||
if _, err := db.sIncrSize(key, 1); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
t.Put(ek, nil)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (db *DB) SAdd(key []byte, args ...[]byte) (int64, error) {
|
||||
t := db.setBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var err error
|
||||
var ek []byte
|
||||
var num int64 = 0
|
||||
for i := 0; i < len(args); i++ {
|
||||
if err := checkSetKMSize(key, args[i]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ek = db.sEncodeSetKey(key, args[i])
|
||||
|
||||
if v, err := db.bucket.Get(ek); err != nil {
|
||||
return 0, err
|
||||
} else if v == nil {
|
||||
num++
|
||||
}
|
||||
|
||||
t.Put(ek, nil)
|
||||
}
|
||||
|
||||
if _, err = db.sIncrSize(key, num); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return num, err
|
||||
|
||||
}
|
||||
|
||||
func (db *DB) SCard(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
sk := db.sEncodeSizeKey(key)
|
||||
|
||||
return Int64(db.bucket.Get(sk))
|
||||
}
|
||||
|
||||
func (db *DB) sDiffGeneric(keys ...[]byte) ([][]byte, error) {
|
||||
destMap := make(map[string]bool)
|
||||
|
||||
members, err := db.SMembers(keys[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range members {
|
||||
destMap[String(m)] = true
|
||||
}
|
||||
|
||||
for _, k := range keys[1:] {
|
||||
members, err := db.SMembers(k)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range members {
|
||||
if _, ok := destMap[String(m)]; !ok {
|
||||
continue
|
||||
} else if ok {
|
||||
delete(destMap, String(m))
|
||||
}
|
||||
}
|
||||
// O - A = O, O is zero set.
|
||||
if len(destMap) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
slice := make([][]byte, len(destMap))
|
||||
idx := 0
|
||||
for k, v := range destMap {
|
||||
if !v {
|
||||
continue
|
||||
}
|
||||
slice[idx] = []byte(k)
|
||||
idx++
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func (db *DB) SDiff(keys ...[]byte) ([][]byte, error) {
|
||||
v, err := db.sDiffGeneric(keys...)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (db *DB) SDiffStore(dstKey []byte, keys ...[]byte) (int64, error) {
|
||||
n, err := db.sStoreGeneric(dstKey, DiffType, keys...)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) sInterGeneric(keys ...[]byte) ([][]byte, error) {
|
||||
destMap := make(map[string]bool)
|
||||
|
||||
members, err := db.SMembers(keys[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, m := range members {
|
||||
destMap[String(m)] = true
|
||||
}
|
||||
|
||||
for _, key := range keys[1:] {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
members, err := db.SMembers(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(members) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempMap := make(map[string]bool)
|
||||
for _, member := range members {
|
||||
if err := checkKeySize(member); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := destMap[String(member)]; ok {
|
||||
tempMap[String(member)] = true //mark this item as selected
|
||||
}
|
||||
}
|
||||
destMap = tempMap //reduce the size of the result set
|
||||
if len(destMap) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
slice := make([][]byte, len(destMap))
|
||||
idx := 0
|
||||
for k, v := range destMap {
|
||||
if !v {
|
||||
continue
|
||||
}
|
||||
|
||||
slice[idx] = []byte(k)
|
||||
idx++
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
|
||||
}
|
||||
|
||||
func (db *DB) SInter(keys ...[]byte) ([][]byte, error) {
|
||||
v, err := db.sInterGeneric(keys...)
|
||||
return v, err
|
||||
|
||||
}
|
||||
|
||||
func (db *DB) SInterStore(dstKey []byte, keys ...[]byte) (int64, error) {
|
||||
n, err := db.sStoreGeneric(dstKey, InterType, keys...)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) SIsMember(key []byte, member []byte) (int64, error) {
|
||||
ek := db.sEncodeSetKey(key, member)
|
||||
|
||||
var n int64 = 1
|
||||
if v, err := db.bucket.Get(ek); err != nil {
|
||||
return 0, err
|
||||
} else if v == nil {
|
||||
n = 0
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (db *DB) SMembers(key []byte) ([][]byte, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
start := db.sEncodeStartKey(key)
|
||||
stop := db.sEncodeStopKey(key)
|
||||
|
||||
v := make([][]byte, 0, 16)
|
||||
|
||||
it := db.bucket.RangeLimitIterator(start, stop, store.RangeROpen, 0, -1)
|
||||
for ; it.Valid(); it.Next() {
|
||||
_, m, err := db.sDecodeSetKey(it.Key())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v = append(v, m)
|
||||
}
|
||||
|
||||
it.Close()
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) SRem(key []byte, args ...[]byte) (int64, error) {
|
||||
t := db.setBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var ek []byte
|
||||
var v []byte
|
||||
var err error
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
defer it.Close()
|
||||
|
||||
var num int64 = 0
|
||||
for i := 0; i < len(args); i++ {
|
||||
if err := checkSetKMSize(key, args[i]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ek = db.sEncodeSetKey(key, args[i])
|
||||
|
||||
v = it.RawFind(ek)
|
||||
if v == nil {
|
||||
continue
|
||||
} else {
|
||||
num++
|
||||
t.Delete(ek)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = db.sIncrSize(key, -num); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return num, err
|
||||
|
||||
}
|
||||
|
||||
func (db *DB) sUnionGeneric(keys ...[]byte) ([][]byte, error) {
|
||||
dstMap := make(map[string]bool)
|
||||
|
||||
for _, key := range keys {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
members, err := db.SMembers(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, member := range members {
|
||||
dstMap[String(member)] = true
|
||||
}
|
||||
}
|
||||
|
||||
slice := make([][]byte, len(dstMap))
|
||||
idx := 0
|
||||
for k, v := range dstMap {
|
||||
if !v {
|
||||
continue
|
||||
}
|
||||
slice[idx] = []byte(k)
|
||||
idx++
|
||||
}
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func (db *DB) SUnion(keys ...[]byte) ([][]byte, error) {
|
||||
v, err := db.sUnionGeneric(keys...)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (db *DB) SUnionStore(dstKey []byte, keys ...[]byte) (int64, error) {
|
||||
n, err := db.sStoreGeneric(dstKey, UnionType, keys...)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) sStoreGeneric(dstKey []byte, optType byte, keys ...[]byte) (int64, error) {
|
||||
if err := checkKeySize(dstKey); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.setBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
db.sDelete(t, dstKey)
|
||||
|
||||
var err error
|
||||
var ek []byte
|
||||
var v [][]byte
|
||||
|
||||
switch optType {
|
||||
case UnionType:
|
||||
v, err = db.sUnionGeneric(keys...)
|
||||
case DiffType:
|
||||
v, err = db.sDiffGeneric(keys...)
|
||||
case InterType:
|
||||
v, err = db.sInterGeneric(keys...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, m := range v {
|
||||
if err := checkSetKMSize(dstKey, m); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ek = db.sEncodeSetKey(dstKey, m)
|
||||
|
||||
if _, err := db.bucket.Get(ek); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t.Put(ek, nil)
|
||||
}
|
||||
|
||||
var num = int64(len(v))
|
||||
sk := db.sEncodeSizeKey(dstKey)
|
||||
t.Put(sk, PutInt64(num))
|
||||
|
||||
if err = t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
||||
func (db *DB) SClear(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.setBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
num := db.sDelete(t, key)
|
||||
db.rmExpire(t, SetType, key)
|
||||
|
||||
err := t.Commit()
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (db *DB) SMclear(keys ...[]byte) (int64, error) {
|
||||
t := db.setBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for _, key := range keys {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
db.sDelete(t, key)
|
||||
db.rmExpire(t, SetType, key)
|
||||
}
|
||||
|
||||
err := t.Commit()
|
||||
return int64(len(keys)), err
|
||||
}
|
||||
|
||||
func (db *DB) SExpire(key []byte, duration int64) (int64, error) {
|
||||
if duration <= 0 {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.sExpireAt(key, time.Now().Unix()+duration)
|
||||
|
||||
}
|
||||
|
||||
func (db *DB) SExpireAt(key []byte, when int64) (int64, error) {
|
||||
if when <= time.Now().Unix() {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.sExpireAt(key, when)
|
||||
|
||||
}
|
||||
|
||||
func (db *DB) STTL(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return db.ttl(SetType, key)
|
||||
}
|
||||
|
||||
func (db *DB) SPersist(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.setBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
n, err := db.rmExpire(t, SetType, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = t.Commit()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (db *DB) SScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
|
||||
return db.scan(SSizeType, key, count, inclusive, match)
|
||||
}
|
195
vendor/github.com/lunny/nodb/t_ttl.go
generated
vendored
Normal file
195
vendor/github.com/lunny/nodb/t_ttl.go
generated
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
var (
|
||||
errExpMetaKey = errors.New("invalid expire meta key")
|
||||
errExpTimeKey = errors.New("invalid expire time key")
|
||||
)
|
||||
|
||||
type retireCallback func(*batch, []byte) int64
|
||||
|
||||
type elimination struct {
|
||||
db *DB
|
||||
exp2Tx []*batch
|
||||
exp2Retire []retireCallback
|
||||
}
|
||||
|
||||
var errExpType = errors.New("invalid expire type")
|
||||
|
||||
func (db *DB) expEncodeTimeKey(dataType byte, key []byte, when int64) []byte {
|
||||
buf := make([]byte, len(key)+11)
|
||||
|
||||
buf[0] = db.index
|
||||
buf[1] = ExpTimeType
|
||||
buf[2] = dataType
|
||||
pos := 3
|
||||
|
||||
binary.BigEndian.PutUint64(buf[pos:], uint64(when))
|
||||
pos += 8
|
||||
|
||||
copy(buf[pos:], key)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) expEncodeMetaKey(dataType byte, key []byte) []byte {
|
||||
buf := make([]byte, len(key)+3)
|
||||
|
||||
buf[0] = db.index
|
||||
buf[1] = ExpMetaType
|
||||
buf[2] = dataType
|
||||
pos := 3
|
||||
|
||||
copy(buf[pos:], key)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) expDecodeMetaKey(mk []byte) (byte, []byte, error) {
|
||||
if len(mk) <= 3 || mk[0] != db.index || mk[1] != ExpMetaType {
|
||||
return 0, nil, errExpMetaKey
|
||||
}
|
||||
|
||||
return mk[2], mk[3:], nil
|
||||
}
|
||||
|
||||
func (db *DB) expDecodeTimeKey(tk []byte) (byte, []byte, int64, error) {
|
||||
if len(tk) < 11 || tk[0] != db.index || tk[1] != ExpTimeType {
|
||||
return 0, nil, 0, errExpTimeKey
|
||||
}
|
||||
|
||||
return tk[2], tk[11:], int64(binary.BigEndian.Uint64(tk[3:])), nil
|
||||
}
|
||||
|
||||
func (db *DB) expire(t *batch, dataType byte, key []byte, duration int64) {
|
||||
db.expireAt(t, dataType, key, time.Now().Unix()+duration)
|
||||
}
|
||||
|
||||
func (db *DB) expireAt(t *batch, dataType byte, key []byte, when int64) {
|
||||
mk := db.expEncodeMetaKey(dataType, key)
|
||||
tk := db.expEncodeTimeKey(dataType, key, when)
|
||||
|
||||
t.Put(tk, mk)
|
||||
t.Put(mk, PutInt64(when))
|
||||
}
|
||||
|
||||
func (db *DB) ttl(dataType byte, key []byte) (t int64, err error) {
|
||||
mk := db.expEncodeMetaKey(dataType, key)
|
||||
|
||||
if t, err = Int64(db.bucket.Get(mk)); err != nil || t == 0 {
|
||||
t = -1
|
||||
} else {
|
||||
t -= time.Now().Unix()
|
||||
if t <= 0 {
|
||||
t = -1
|
||||
}
|
||||
// if t == -1 : to remove ????
|
||||
}
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
func (db *DB) rmExpire(t *batch, dataType byte, key []byte) (int64, error) {
|
||||
mk := db.expEncodeMetaKey(dataType, key)
|
||||
if v, err := db.bucket.Get(mk); err != nil {
|
||||
return 0, err
|
||||
} else if v == nil {
|
||||
return 0, nil
|
||||
} else if when, err2 := Int64(v, nil); err2 != nil {
|
||||
return 0, err2
|
||||
} else {
|
||||
tk := db.expEncodeTimeKey(dataType, key, when)
|
||||
t.Delete(mk)
|
||||
t.Delete(tk)
|
||||
return 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) expFlush(t *batch, dataType byte) (err error) {
|
||||
minKey := make([]byte, 3)
|
||||
minKey[0] = db.index
|
||||
minKey[1] = ExpTimeType
|
||||
minKey[2] = dataType
|
||||
|
||||
maxKey := make([]byte, 3)
|
||||
maxKey[0] = db.index
|
||||
maxKey[1] = ExpMetaType
|
||||
maxKey[2] = dataType + 1
|
||||
|
||||
_, err = db.flushRegion(t, minKey, maxKey)
|
||||
err = t.Commit()
|
||||
return
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
func newEliminator(db *DB) *elimination {
|
||||
eli := new(elimination)
|
||||
eli.db = db
|
||||
eli.exp2Tx = make([]*batch, maxDataType)
|
||||
eli.exp2Retire = make([]retireCallback, maxDataType)
|
||||
return eli
|
||||
}
|
||||
|
||||
func (eli *elimination) regRetireContext(dataType byte, t *batch, onRetire retireCallback) {
|
||||
|
||||
// todo .. need to ensure exist - mapExpMetaType[expType]
|
||||
|
||||
eli.exp2Tx[dataType] = t
|
||||
eli.exp2Retire[dataType] = onRetire
|
||||
}
|
||||
|
||||
// call by outside ... (from *db to another *db)
|
||||
func (eli *elimination) active() {
|
||||
now := time.Now().Unix()
|
||||
db := eli.db
|
||||
dbGet := db.bucket.Get
|
||||
|
||||
minKey := db.expEncodeTimeKey(NoneType, nil, 0)
|
||||
maxKey := db.expEncodeTimeKey(maxDataType, nil, now)
|
||||
|
||||
it := db.bucket.RangeLimitIterator(minKey, maxKey, store.RangeROpen, 0, -1)
|
||||
for ; it.Valid(); it.Next() {
|
||||
tk := it.RawKey()
|
||||
mk := it.RawValue()
|
||||
|
||||
dt, k, _, err := db.expDecodeTimeKey(tk)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
t := eli.exp2Tx[dt]
|
||||
onRetire := eli.exp2Retire[dt]
|
||||
if tk == nil || onRetire == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
t.Lock()
|
||||
|
||||
if exp, err := Int64(dbGet(mk)); err == nil {
|
||||
// check expire again
|
||||
if exp <= now {
|
||||
onRetire(t, k)
|
||||
t.Delete(tk)
|
||||
t.Delete(mk)
|
||||
|
||||
t.Commit()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
t.Unlock()
|
||||
}
|
||||
it.Close()
|
||||
|
||||
return
|
||||
}
|
943
vendor/github.com/lunny/nodb/t_zset.go
generated
vendored
Normal file
943
vendor/github.com/lunny/nodb/t_zset.go
generated
vendored
Normal file
@ -0,0 +1,943 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
const (
|
||||
MinScore int64 = -1<<63 + 1
|
||||
MaxScore int64 = 1<<63 - 1
|
||||
InvalidScore int64 = -1 << 63
|
||||
|
||||
AggregateSum byte = 0
|
||||
AggregateMin byte = 1
|
||||
AggregateMax byte = 2
|
||||
)
|
||||
|
||||
type ScorePair struct {
|
||||
Score int64
|
||||
Member []byte
|
||||
}
|
||||
|
||||
var errZSizeKey = errors.New("invalid zsize key")
|
||||
var errZSetKey = errors.New("invalid zset key")
|
||||
var errZScoreKey = errors.New("invalid zscore key")
|
||||
var errScoreOverflow = errors.New("zset score overflow")
|
||||
var errInvalidAggregate = errors.New("invalid aggregate")
|
||||
var errInvalidWeightNum = errors.New("invalid weight number")
|
||||
var errInvalidSrcKeyNum = errors.New("invalid src key number")
|
||||
|
||||
const (
|
||||
zsetNScoreSep byte = '<'
|
||||
zsetPScoreSep byte = zsetNScoreSep + 1
|
||||
zsetStopScoreSep byte = zsetPScoreSep + 1
|
||||
|
||||
zsetStartMemSep byte = ':'
|
||||
zsetStopMemSep byte = zsetStartMemSep + 1
|
||||
)
|
||||
|
||||
func checkZSetKMSize(key []byte, member []byte) error {
|
||||
if len(key) > MaxKeySize || len(key) == 0 {
|
||||
return errKeySize
|
||||
} else if len(member) > MaxZSetMemberSize || len(member) == 0 {
|
||||
return errZSetMemberSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) zEncodeSizeKey(key []byte) []byte {
|
||||
buf := make([]byte, len(key)+2)
|
||||
buf[0] = db.index
|
||||
buf[1] = ZSizeType
|
||||
|
||||
copy(buf[2:], key)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) zDecodeSizeKey(ek []byte) ([]byte, error) {
|
||||
if len(ek) < 2 || ek[0] != db.index || ek[1] != ZSizeType {
|
||||
return nil, errZSizeKey
|
||||
}
|
||||
|
||||
return ek[2:], nil
|
||||
}
|
||||
|
||||
func (db *DB) zEncodeSetKey(key []byte, member []byte) []byte {
|
||||
buf := make([]byte, len(key)+len(member)+5)
|
||||
|
||||
pos := 0
|
||||
buf[pos] = db.index
|
||||
pos++
|
||||
|
||||
buf[pos] = ZSetType
|
||||
pos++
|
||||
|
||||
binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
|
||||
pos += 2
|
||||
|
||||
copy(buf[pos:], key)
|
||||
pos += len(key)
|
||||
|
||||
buf[pos] = zsetStartMemSep
|
||||
pos++
|
||||
|
||||
copy(buf[pos:], member)
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) zDecodeSetKey(ek []byte) ([]byte, []byte, error) {
|
||||
if len(ek) < 5 || ek[0] != db.index || ek[1] != ZSetType {
|
||||
return nil, nil, errZSetKey
|
||||
}
|
||||
|
||||
keyLen := int(binary.BigEndian.Uint16(ek[2:]))
|
||||
if keyLen+5 > len(ek) {
|
||||
return nil, nil, errZSetKey
|
||||
}
|
||||
|
||||
key := ek[4 : 4+keyLen]
|
||||
|
||||
if ek[4+keyLen] != zsetStartMemSep {
|
||||
return nil, nil, errZSetKey
|
||||
}
|
||||
|
||||
member := ek[5+keyLen:]
|
||||
return key, member, nil
|
||||
}
|
||||
|
||||
func (db *DB) zEncodeStartSetKey(key []byte) []byte {
|
||||
k := db.zEncodeSetKey(key, nil)
|
||||
return k
|
||||
}
|
||||
|
||||
func (db *DB) zEncodeStopSetKey(key []byte) []byte {
|
||||
k := db.zEncodeSetKey(key, nil)
|
||||
k[len(k)-1] = zsetStartMemSep + 1
|
||||
return k
|
||||
}
|
||||
|
||||
func (db *DB) zEncodeScoreKey(key []byte, member []byte, score int64) []byte {
|
||||
buf := make([]byte, len(key)+len(member)+14)
|
||||
|
||||
pos := 0
|
||||
buf[pos] = db.index
|
||||
pos++
|
||||
|
||||
buf[pos] = ZScoreType
|
||||
pos++
|
||||
|
||||
binary.BigEndian.PutUint16(buf[pos:], uint16(len(key)))
|
||||
pos += 2
|
||||
|
||||
copy(buf[pos:], key)
|
||||
pos += len(key)
|
||||
|
||||
if score < 0 {
|
||||
buf[pos] = zsetNScoreSep
|
||||
} else {
|
||||
buf[pos] = zsetPScoreSep
|
||||
}
|
||||
|
||||
pos++
|
||||
binary.BigEndian.PutUint64(buf[pos:], uint64(score))
|
||||
pos += 8
|
||||
|
||||
buf[pos] = zsetStartMemSep
|
||||
pos++
|
||||
|
||||
copy(buf[pos:], member)
|
||||
return buf
|
||||
}
|
||||
|
||||
func (db *DB) zEncodeStartScoreKey(key []byte, score int64) []byte {
|
||||
return db.zEncodeScoreKey(key, nil, score)
|
||||
}
|
||||
|
||||
func (db *DB) zEncodeStopScoreKey(key []byte, score int64) []byte {
|
||||
k := db.zEncodeScoreKey(key, nil, score)
|
||||
k[len(k)-1] = zsetStopMemSep
|
||||
return k
|
||||
}
|
||||
|
||||
func (db *DB) zDecodeScoreKey(ek []byte) (key []byte, member []byte, score int64, err error) {
|
||||
if len(ek) < 14 || ek[0] != db.index || ek[1] != ZScoreType {
|
||||
err = errZScoreKey
|
||||
return
|
||||
}
|
||||
|
||||
keyLen := int(binary.BigEndian.Uint16(ek[2:]))
|
||||
if keyLen+14 > len(ek) {
|
||||
err = errZScoreKey
|
||||
return
|
||||
}
|
||||
|
||||
key = ek[4 : 4+keyLen]
|
||||
pos := 4 + keyLen
|
||||
|
||||
if (ek[pos] != zsetNScoreSep) && (ek[pos] != zsetPScoreSep) {
|
||||
err = errZScoreKey
|
||||
return
|
||||
}
|
||||
pos++
|
||||
|
||||
score = int64(binary.BigEndian.Uint64(ek[pos:]))
|
||||
pos += 8
|
||||
|
||||
if ek[pos] != zsetStartMemSep {
|
||||
err = errZScoreKey
|
||||
return
|
||||
}
|
||||
|
||||
pos++
|
||||
|
||||
member = ek[pos:]
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) zSetItem(t *batch, key []byte, score int64, member []byte) (int64, error) {
|
||||
if score <= MinScore || score >= MaxScore {
|
||||
return 0, errScoreOverflow
|
||||
}
|
||||
|
||||
var exists int64 = 0
|
||||
ek := db.zEncodeSetKey(key, member)
|
||||
|
||||
if v, err := db.bucket.Get(ek); err != nil {
|
||||
return 0, err
|
||||
} else if v != nil {
|
||||
exists = 1
|
||||
|
||||
if s, err := Int64(v, err); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
sk := db.zEncodeScoreKey(key, member, s)
|
||||
t.Delete(sk)
|
||||
}
|
||||
}
|
||||
|
||||
t.Put(ek, PutInt64(score))
|
||||
|
||||
sk := db.zEncodeScoreKey(key, member, score)
|
||||
t.Put(sk, []byte{})
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (db *DB) zDelItem(t *batch, key []byte, member []byte, skipDelScore bool) (int64, error) {
|
||||
ek := db.zEncodeSetKey(key, member)
|
||||
if v, err := db.bucket.Get(ek); err != nil {
|
||||
return 0, err
|
||||
} else if v == nil {
|
||||
//not exists
|
||||
return 0, nil
|
||||
} else {
|
||||
//exists
|
||||
if !skipDelScore {
|
||||
//we must del score
|
||||
if s, err := Int64(v, err); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
sk := db.zEncodeScoreKey(key, member, s)
|
||||
t.Delete(sk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.Delete(ek)
|
||||
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (db *DB) zDelete(t *batch, key []byte) int64 {
|
||||
delMembCnt, _ := db.zRemRange(t, key, MinScore, MaxScore, 0, -1)
|
||||
// todo : log err
|
||||
return delMembCnt
|
||||
}
|
||||
|
||||
func (db *DB) zExpireAt(key []byte, when int64) (int64, error) {
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
if zcnt, err := db.ZCard(key); err != nil || zcnt == 0 {
|
||||
return 0, err
|
||||
} else {
|
||||
db.expireAt(t, ZSetType, key, when)
|
||||
if err := t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 1, nil
|
||||
}
|
||||
|
||||
func (db *DB) ZAdd(key []byte, args ...ScorePair) (int64, error) {
|
||||
if len(args) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var num int64 = 0
|
||||
for i := 0; i < len(args); i++ {
|
||||
score := args[i].Score
|
||||
member := args[i].Member
|
||||
|
||||
if err := checkZSetKMSize(key, member); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if n, err := db.zSetItem(t, key, score, member); err != nil {
|
||||
return 0, err
|
||||
} else if n == 0 {
|
||||
//add new
|
||||
num++
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := db.zIncrSize(t, key, num); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//todo add binlog
|
||||
err := t.Commit()
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (db *DB) zIncrSize(t *batch, key []byte, delta int64) (int64, error) {
|
||||
sk := db.zEncodeSizeKey(key)
|
||||
|
||||
size, err := Int64(db.bucket.Get(sk))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
size += delta
|
||||
if size <= 0 {
|
||||
size = 0
|
||||
t.Delete(sk)
|
||||
db.rmExpire(t, ZSetType, key)
|
||||
} else {
|
||||
t.Put(sk, PutInt64(size))
|
||||
}
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
func (db *DB) ZCard(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
sk := db.zEncodeSizeKey(key)
|
||||
return Int64(db.bucket.Get(sk))
|
||||
}
|
||||
|
||||
func (db *DB) ZScore(key []byte, member []byte) (int64, error) {
|
||||
if err := checkZSetKMSize(key, member); err != nil {
|
||||
return InvalidScore, err
|
||||
}
|
||||
|
||||
var score int64 = InvalidScore
|
||||
|
||||
k := db.zEncodeSetKey(key, member)
|
||||
if v, err := db.bucket.Get(k); err != nil {
|
||||
return InvalidScore, err
|
||||
} else if v == nil {
|
||||
return InvalidScore, ErrScoreMiss
|
||||
} else {
|
||||
if score, err = Int64(v, nil); err != nil {
|
||||
return InvalidScore, err
|
||||
}
|
||||
}
|
||||
|
||||
return score, nil
|
||||
}
|
||||
|
||||
func (db *DB) ZRem(key []byte, members ...[]byte) (int64, error) {
|
||||
if len(members) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var num int64 = 0
|
||||
for i := 0; i < len(members); i++ {
|
||||
if err := checkZSetKMSize(key, members[i]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if n, err := db.zDelItem(t, key, members[i], false); err != nil {
|
||||
return 0, err
|
||||
} else if n == 1 {
|
||||
num++
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := db.zIncrSize(t, key, -num); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err := t.Commit()
|
||||
return num, err
|
||||
}
|
||||
|
||||
func (db *DB) ZIncrBy(key []byte, delta int64, member []byte) (int64, error) {
|
||||
if err := checkZSetKMSize(key, member); err != nil {
|
||||
return InvalidScore, err
|
||||
}
|
||||
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
ek := db.zEncodeSetKey(key, member)
|
||||
|
||||
var oldScore int64 = 0
|
||||
v, err := db.bucket.Get(ek)
|
||||
if err != nil {
|
||||
return InvalidScore, err
|
||||
} else if v == nil {
|
||||
db.zIncrSize(t, key, 1)
|
||||
} else {
|
||||
if oldScore, err = Int64(v, err); err != nil {
|
||||
return InvalidScore, err
|
||||
}
|
||||
}
|
||||
|
||||
newScore := oldScore + delta
|
||||
if newScore >= MaxScore || newScore <= MinScore {
|
||||
return InvalidScore, errScoreOverflow
|
||||
}
|
||||
|
||||
sk := db.zEncodeScoreKey(key, member, newScore)
|
||||
t.Put(sk, []byte{})
|
||||
t.Put(ek, PutInt64(newScore))
|
||||
|
||||
if v != nil {
|
||||
// so as to update score, we must delete the old one
|
||||
oldSk := db.zEncodeScoreKey(key, member, oldScore)
|
||||
t.Delete(oldSk)
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return newScore, err
|
||||
}
|
||||
|
||||
func (db *DB) ZCount(key []byte, min int64, max int64) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
minKey := db.zEncodeStartScoreKey(key, min)
|
||||
maxKey := db.zEncodeStopScoreKey(key, max)
|
||||
|
||||
rangeType := store.RangeROpen
|
||||
|
||||
it := db.bucket.RangeLimitIterator(minKey, maxKey, rangeType, 0, -1)
|
||||
var n int64 = 0
|
||||
for ; it.Valid(); it.Next() {
|
||||
n++
|
||||
}
|
||||
it.Close()
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (db *DB) zrank(key []byte, member []byte, reverse bool) (int64, error) {
|
||||
if err := checkZSetKMSize(key, member); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
k := db.zEncodeSetKey(key, member)
|
||||
|
||||
it := db.bucket.NewIterator()
|
||||
defer it.Close()
|
||||
|
||||
if v := it.Find(k); v == nil {
|
||||
return -1, nil
|
||||
} else {
|
||||
if s, err := Int64(v, nil); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
var rit *store.RangeLimitIterator
|
||||
|
||||
sk := db.zEncodeScoreKey(key, member, s)
|
||||
|
||||
if !reverse {
|
||||
minKey := db.zEncodeStartScoreKey(key, MinScore)
|
||||
|
||||
rit = store.NewRangeIterator(it, &store.Range{minKey, sk, store.RangeClose})
|
||||
} else {
|
||||
maxKey := db.zEncodeStopScoreKey(key, MaxScore)
|
||||
rit = store.NewRevRangeIterator(it, &store.Range{sk, maxKey, store.RangeClose})
|
||||
}
|
||||
|
||||
var lastKey []byte = nil
|
||||
var n int64 = 0
|
||||
|
||||
for ; rit.Valid(); rit.Next() {
|
||||
n++
|
||||
|
||||
lastKey = rit.BufKey(lastKey)
|
||||
}
|
||||
|
||||
if _, m, _, err := db.zDecodeScoreKey(lastKey); err == nil && bytes.Equal(m, member) {
|
||||
n--
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (db *DB) zIterator(key []byte, min int64, max int64, offset int, count int, reverse bool) *store.RangeLimitIterator {
|
||||
minKey := db.zEncodeStartScoreKey(key, min)
|
||||
maxKey := db.zEncodeStopScoreKey(key, max)
|
||||
|
||||
if !reverse {
|
||||
return db.bucket.RangeLimitIterator(minKey, maxKey, store.RangeClose, offset, count)
|
||||
} else {
|
||||
return db.bucket.RevRangeLimitIterator(minKey, maxKey, store.RangeClose, offset, count)
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) zRemRange(t *batch, key []byte, min int64, max int64, offset int, count int) (int64, error) {
|
||||
if len(key) > MaxKeySize {
|
||||
return 0, errKeySize
|
||||
}
|
||||
|
||||
it := db.zIterator(key, min, max, offset, count, false)
|
||||
var num int64 = 0
|
||||
for ; it.Valid(); it.Next() {
|
||||
sk := it.RawKey()
|
||||
_, m, _, err := db.zDecodeScoreKey(sk)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if n, err := db.zDelItem(t, key, m, true); err != nil {
|
||||
return 0, err
|
||||
} else if n == 1 {
|
||||
num++
|
||||
}
|
||||
|
||||
t.Delete(sk)
|
||||
}
|
||||
it.Close()
|
||||
|
||||
if _, err := db.zIncrSize(t, key, -num); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return num, nil
|
||||
}
|
||||
|
||||
func (db *DB) zRange(key []byte, min int64, max int64, offset int, count int, reverse bool) ([]ScorePair, error) {
|
||||
if len(key) > MaxKeySize {
|
||||
return nil, errKeySize
|
||||
}
|
||||
|
||||
if offset < 0 {
|
||||
return []ScorePair{}, nil
|
||||
}
|
||||
|
||||
nv := 64
|
||||
if count > 0 {
|
||||
nv = count
|
||||
}
|
||||
|
||||
v := make([]ScorePair, 0, nv)
|
||||
|
||||
var it *store.RangeLimitIterator
|
||||
|
||||
//if reverse and offset is 0, count < 0, we may use forward iterator then reverse
|
||||
//because store iterator prev is slower than next
|
||||
if !reverse || (offset == 0 && count < 0) {
|
||||
it = db.zIterator(key, min, max, offset, count, false)
|
||||
} else {
|
||||
it = db.zIterator(key, min, max, offset, count, true)
|
||||
}
|
||||
|
||||
for ; it.Valid(); it.Next() {
|
||||
_, m, s, err := db.zDecodeScoreKey(it.Key())
|
||||
//may be we will check key equal?
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
v = append(v, ScorePair{Member: m, Score: s})
|
||||
}
|
||||
it.Close()
|
||||
|
||||
if reverse && (offset == 0 && count < 0) {
|
||||
for i, j := 0, len(v)-1; i < j; i, j = i+1, j-1 {
|
||||
v[i], v[j] = v[j], v[i]
|
||||
}
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (db *DB) zParseLimit(key []byte, start int, stop int) (offset int, count int, err error) {
|
||||
if start < 0 || stop < 0 {
|
||||
//refer redis implementation
|
||||
var size int64
|
||||
size, err = db.ZCard(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
llen := int(size)
|
||||
|
||||
if start < 0 {
|
||||
start = llen + start
|
||||
}
|
||||
if stop < 0 {
|
||||
stop = llen + stop
|
||||
}
|
||||
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
|
||||
if start >= llen {
|
||||
offset = -1
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if start > stop {
|
||||
offset = -1
|
||||
return
|
||||
}
|
||||
|
||||
offset = start
|
||||
count = (stop - start) + 1
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) ZClear(key []byte) (int64, error) {
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
rmCnt, err := db.zRemRange(t, key, MinScore, MaxScore, 0, -1)
|
||||
if err == nil {
|
||||
err = t.Commit()
|
||||
}
|
||||
|
||||
return rmCnt, err
|
||||
}
|
||||
|
||||
func (db *DB) ZMclear(keys ...[]byte) (int64, error) {
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for _, key := range keys {
|
||||
if _, err := db.zRemRange(t, key, MinScore, MaxScore, 0, -1); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
err := t.Commit()
|
||||
|
||||
return int64(len(keys)), err
|
||||
}
|
||||
|
||||
func (db *DB) ZRange(key []byte, start int, stop int) ([]ScorePair, error) {
|
||||
return db.ZRangeGeneric(key, start, stop, false)
|
||||
}
|
||||
|
||||
//min and max must be inclusive
|
||||
//if no limit, set offset = 0 and count = -1
|
||||
func (db *DB) ZRangeByScore(key []byte, min int64, max int64,
|
||||
offset int, count int) ([]ScorePair, error) {
|
||||
return db.ZRangeByScoreGeneric(key, min, max, offset, count, false)
|
||||
}
|
||||
|
||||
func (db *DB) ZRank(key []byte, member []byte) (int64, error) {
|
||||
return db.zrank(key, member, false)
|
||||
}
|
||||
|
||||
func (db *DB) ZRemRangeByRank(key []byte, start int, stop int) (int64, error) {
|
||||
offset, count, err := db.zParseLimit(key, start, stop)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var rmCnt int64
|
||||
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
rmCnt, err = db.zRemRange(t, key, MinScore, MaxScore, offset, count)
|
||||
if err == nil {
|
||||
err = t.Commit()
|
||||
}
|
||||
|
||||
return rmCnt, err
|
||||
}
|
||||
|
||||
//min and max must be inclusive
|
||||
func (db *DB) ZRemRangeByScore(key []byte, min int64, max int64) (int64, error) {
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
rmCnt, err := db.zRemRange(t, key, min, max, 0, -1)
|
||||
if err == nil {
|
||||
err = t.Commit()
|
||||
}
|
||||
|
||||
return rmCnt, err
|
||||
}
|
||||
|
||||
func (db *DB) ZRevRange(key []byte, start int, stop int) ([]ScorePair, error) {
|
||||
return db.ZRangeGeneric(key, start, stop, true)
|
||||
}
|
||||
|
||||
func (db *DB) ZRevRank(key []byte, member []byte) (int64, error) {
|
||||
return db.zrank(key, member, true)
|
||||
}
|
||||
|
||||
//min and max must be inclusive
|
||||
//if no limit, set offset = 0 and count = -1
|
||||
func (db *DB) ZRevRangeByScore(key []byte, min int64, max int64, offset int, count int) ([]ScorePair, error) {
|
||||
return db.ZRangeByScoreGeneric(key, min, max, offset, count, true)
|
||||
}
|
||||
|
||||
func (db *DB) ZRangeGeneric(key []byte, start int, stop int, reverse bool) ([]ScorePair, error) {
|
||||
offset, count, err := db.zParseLimit(key, start, stop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db.zRange(key, MinScore, MaxScore, offset, count, reverse)
|
||||
}
|
||||
|
||||
//min and max must be inclusive
|
||||
//if no limit, set offset = 0 and count = -1
|
||||
func (db *DB) ZRangeByScoreGeneric(key []byte, min int64, max int64,
|
||||
offset int, count int, reverse bool) ([]ScorePair, error) {
|
||||
|
||||
return db.zRange(key, min, max, offset, count, reverse)
|
||||
}
|
||||
|
||||
func (db *DB) zFlush() (drop int64, err error) {
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
return db.flushType(t, ZSetType)
|
||||
}
|
||||
|
||||
func (db *DB) ZExpire(key []byte, duration int64) (int64, error) {
|
||||
if duration <= 0 {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.zExpireAt(key, time.Now().Unix()+duration)
|
||||
}
|
||||
|
||||
func (db *DB) ZExpireAt(key []byte, when int64) (int64, error) {
|
||||
if when <= time.Now().Unix() {
|
||||
return 0, errExpireValue
|
||||
}
|
||||
|
||||
return db.zExpireAt(key, when)
|
||||
}
|
||||
|
||||
func (db *DB) ZTTL(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
return db.ttl(ZSetType, key)
|
||||
}
|
||||
|
||||
func (db *DB) ZPersist(key []byte) (int64, error) {
|
||||
if err := checkKeySize(key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
n, err := db.rmExpire(t, ZSetType, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = t.Commit()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func getAggregateFunc(aggregate byte) func(int64, int64) int64 {
|
||||
switch aggregate {
|
||||
case AggregateSum:
|
||||
return func(a int64, b int64) int64 {
|
||||
return a + b
|
||||
}
|
||||
case AggregateMax:
|
||||
return func(a int64, b int64) int64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
case AggregateMin:
|
||||
return func(a int64, b int64) int64 {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) ZUnionStore(destKey []byte, srcKeys [][]byte, weights []int64, aggregate byte) (int64, error) {
|
||||
|
||||
var destMap = map[string]int64{}
|
||||
aggregateFunc := getAggregateFunc(aggregate)
|
||||
if aggregateFunc == nil {
|
||||
return 0, errInvalidAggregate
|
||||
}
|
||||
if len(srcKeys) < 1 {
|
||||
return 0, errInvalidSrcKeyNum
|
||||
}
|
||||
if weights != nil {
|
||||
if len(srcKeys) != len(weights) {
|
||||
return 0, errInvalidWeightNum
|
||||
}
|
||||
} else {
|
||||
weights = make([]int64, len(srcKeys))
|
||||
for i := 0; i < len(weights); i++ {
|
||||
weights[i] = 1
|
||||
}
|
||||
}
|
||||
|
||||
for i, key := range srcKeys {
|
||||
scorePairs, err := db.ZRange(key, 0, -1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, pair := range scorePairs {
|
||||
if score, ok := destMap[String(pair.Member)]; !ok {
|
||||
destMap[String(pair.Member)] = pair.Score * weights[i]
|
||||
} else {
|
||||
destMap[String(pair.Member)] = aggregateFunc(score, pair.Score*weights[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
db.zDelete(t, destKey)
|
||||
|
||||
for member, score := range destMap {
|
||||
if err := checkZSetKMSize(destKey, []byte(member)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if _, err := db.zSetItem(t, destKey, score, []byte(member)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
var num = int64(len(destMap))
|
||||
sk := db.zEncodeSizeKey(destKey)
|
||||
t.Put(sk, PutInt64(num))
|
||||
|
||||
//todo add binlog
|
||||
if err := t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
||||
func (db *DB) ZInterStore(destKey []byte, srcKeys [][]byte, weights []int64, aggregate byte) (int64, error) {
|
||||
|
||||
aggregateFunc := getAggregateFunc(aggregate)
|
||||
if aggregateFunc == nil {
|
||||
return 0, errInvalidAggregate
|
||||
}
|
||||
if len(srcKeys) < 1 {
|
||||
return 0, errInvalidSrcKeyNum
|
||||
}
|
||||
if weights != nil {
|
||||
if len(srcKeys) != len(weights) {
|
||||
return 0, errInvalidWeightNum
|
||||
}
|
||||
} else {
|
||||
weights = make([]int64, len(srcKeys))
|
||||
for i := 0; i < len(weights); i++ {
|
||||
weights[i] = 1
|
||||
}
|
||||
}
|
||||
|
||||
var destMap = map[string]int64{}
|
||||
scorePairs, err := db.ZRange(srcKeys[0], 0, -1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, pair := range scorePairs {
|
||||
destMap[String(pair.Member)] = pair.Score * weights[0]
|
||||
}
|
||||
|
||||
for i, key := range srcKeys[1:] {
|
||||
scorePairs, err := db.ZRange(key, 0, -1)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
tmpMap := map[string]int64{}
|
||||
for _, pair := range scorePairs {
|
||||
if score, ok := destMap[String(pair.Member)]; ok {
|
||||
tmpMap[String(pair.Member)] = aggregateFunc(score, pair.Score*weights[i+1])
|
||||
}
|
||||
}
|
||||
destMap = tmpMap
|
||||
}
|
||||
|
||||
t := db.zsetBatch
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
db.zDelete(t, destKey)
|
||||
|
||||
for member, score := range destMap {
|
||||
if err := checkZSetKMSize(destKey, []byte(member)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := db.zSetItem(t, destKey, score, []byte(member)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
var num int64 = int64(len(destMap))
|
||||
sk := db.zEncodeSizeKey(destKey)
|
||||
t.Put(sk, PutInt64(num))
|
||||
//todo add binlog
|
||||
if err := t.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
||||
func (db *DB) ZScan(key []byte, count int, inclusive bool, match string) ([][]byte, error) {
|
||||
return db.scan(ZSizeType, key, count, inclusive, match)
|
||||
}
|
113
vendor/github.com/lunny/nodb/tx.go
generated
vendored
Normal file
113
vendor/github.com/lunny/nodb/tx.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/lunny/nodb/store"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNestTx = errors.New("nest transaction not supported")
|
||||
ErrTxDone = errors.New("Transaction has already been committed or rolled back")
|
||||
)
|
||||
|
||||
type Tx struct {
|
||||
*DB
|
||||
|
||||
tx *store.Tx
|
||||
|
||||
logs [][]byte
|
||||
}
|
||||
|
||||
func (db *DB) IsTransaction() bool {
|
||||
return db.status == DBInTransaction
|
||||
}
|
||||
|
||||
// Begin a transaction, it will block all other write operations before calling Commit or Rollback.
|
||||
// You must be very careful to prevent long-time transaction.
|
||||
func (db *DB) Begin() (*Tx, error) {
|
||||
if db.IsTransaction() {
|
||||
return nil, ErrNestTx
|
||||
}
|
||||
|
||||
tx := new(Tx)
|
||||
|
||||
tx.DB = new(DB)
|
||||
tx.DB.l = db.l
|
||||
|
||||
tx.l.wLock.Lock()
|
||||
|
||||
tx.DB.sdb = db.sdb
|
||||
|
||||
var err error
|
||||
tx.tx, err = db.sdb.Begin()
|
||||
if err != nil {
|
||||
tx.l.wLock.Unlock()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx.DB.bucket = tx.tx
|
||||
|
||||
tx.DB.status = DBInTransaction
|
||||
|
||||
tx.DB.index = db.index
|
||||
|
||||
tx.DB.kvBatch = tx.newBatch()
|
||||
tx.DB.listBatch = tx.newBatch()
|
||||
tx.DB.hashBatch = tx.newBatch()
|
||||
tx.DB.zsetBatch = tx.newBatch()
|
||||
tx.DB.binBatch = tx.newBatch()
|
||||
tx.DB.setBatch = tx.newBatch()
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
func (tx *Tx) Commit() error {
|
||||
if tx.tx == nil {
|
||||
return ErrTxDone
|
||||
}
|
||||
|
||||
tx.l.commitLock.Lock()
|
||||
err := tx.tx.Commit()
|
||||
tx.tx = nil
|
||||
|
||||
if len(tx.logs) > 0 {
|
||||
tx.l.binlog.Log(tx.logs...)
|
||||
}
|
||||
|
||||
tx.l.commitLock.Unlock()
|
||||
|
||||
tx.l.wLock.Unlock()
|
||||
|
||||
tx.DB.bucket = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (tx *Tx) Rollback() error {
|
||||
if tx.tx == nil {
|
||||
return ErrTxDone
|
||||
}
|
||||
|
||||
err := tx.tx.Rollback()
|
||||
tx.tx = nil
|
||||
|
||||
tx.l.wLock.Unlock()
|
||||
tx.DB.bucket = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (tx *Tx) newBatch() *batch {
|
||||
return tx.l.newBatch(tx.tx.NewWriteBatch(), &txBatchLocker{}, tx)
|
||||
}
|
||||
|
||||
func (tx *Tx) Select(index int) error {
|
||||
if index < 0 || index >= int(MaxDBNumber) {
|
||||
return fmt.Errorf("invalid db index %d", index)
|
||||
}
|
||||
|
||||
tx.DB.index = uint8(index)
|
||||
return nil
|
||||
}
|
113
vendor/github.com/lunny/nodb/util.go
generated
vendored
Normal file
113
vendor/github.com/lunny/nodb/util.go
generated
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
package nodb
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var errIntNumber = errors.New("invalid integer")
|
||||
|
||||
// no copy to change slice to string
|
||||
// use your own risk
|
||||
func String(b []byte) (s string) {
|
||||
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
pstring.Data = pbytes.Data
|
||||
pstring.Len = pbytes.Len
|
||||
return
|
||||
}
|
||||
|
||||
// no copy to change string to slice
|
||||
// use your own risk
|
||||
func Slice(s string) (b []byte) {
|
||||
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
pstring := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
pbytes.Data = pstring.Data
|
||||
pbytes.Len = pstring.Len
|
||||
pbytes.Cap = pstring.Len
|
||||
return
|
||||
}
|
||||
|
||||
func Int64(v []byte, err error) (int64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if v == nil || len(v) == 0 {
|
||||
return 0, nil
|
||||
} else if len(v) != 8 {
|
||||
return 0, errIntNumber
|
||||
}
|
||||
|
||||
return int64(binary.LittleEndian.Uint64(v)), nil
|
||||
}
|
||||
|
||||
func PutInt64(v int64) []byte {
|
||||
var b []byte
|
||||
pbytes := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
pbytes.Data = uintptr(unsafe.Pointer(&v))
|
||||
pbytes.Len = 8
|
||||
pbytes.Cap = 8
|
||||
return b
|
||||
}
|
||||
|
||||
func StrInt64(v []byte, err error) (int64, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if v == nil {
|
||||
return 0, nil
|
||||
} else {
|
||||
return strconv.ParseInt(String(v), 10, 64)
|
||||
}
|
||||
}
|
||||
|
||||
func StrInt32(v []byte, err error) (int32, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if v == nil {
|
||||
return 0, nil
|
||||
} else {
|
||||
res, err := strconv.ParseInt(String(v), 10, 32)
|
||||
return int32(res), err
|
||||
}
|
||||
}
|
||||
|
||||
func StrInt8(v []byte, err error) (int8, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if v == nil {
|
||||
return 0, nil
|
||||
} else {
|
||||
res, err := strconv.ParseInt(String(v), 10, 8)
|
||||
return int8(res), err
|
||||
}
|
||||
}
|
||||
|
||||
func StrPutInt64(v int64) []byte {
|
||||
return strconv.AppendInt(nil, v, 10)
|
||||
}
|
||||
|
||||
func MinUInt32(a uint32, b uint32) uint32 {
|
||||
if a > b {
|
||||
return b
|
||||
} else {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
func MaxUInt32(a uint32, b uint32) uint32 {
|
||||
if a > b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func MaxInt32(a int32, b int32) int32 {
|
||||
if a > b {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Normal file
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
282
vendor/github.com/pkg/errors/errors.go
generated
vendored
Normal file
282
vendor/github.com/pkg/errors/errors.go
generated
vendored
Normal file
@ -0,0 +1,282 @@
|
||||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which when applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// together with the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required, the errors.WithStack and
|
||||
// errors.WithMessage functions destructure errors.Wrap into its component
|
||||
// operations: annotating an error with a stack trace and with a message,
|
||||
// respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error that does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// Although the causer interface is not exported by this package, it is
|
||||
// considered a part of its stable public interface.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported:
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively.
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface:
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// The returned errors.StackTrace type is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Although the stackTracer interface is not exported by this package, it is
|
||||
// considered a part of its stable public interface.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is called, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessagef annotates err with the format specifier.
|
||||
// If err is nil, WithMessagef returns nil.
|
||||
func WithMessagef(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
147
vendor/github.com/pkg/errors/stack.go
generated
vendored
Normal file
147
vendor/github.com/pkg/errors/stack.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s function name and path of source file relative to the compile time
|
||||
// GOPATH separated by \n\t (<funcname>\n\t<path>)
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s lists source files for each Frame in the stack
|
||||
// %v lists the source file and line number for each Frame in the stack
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+v Prints filename, function, and line number for each Frame in the stack.
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
12
vendor/github.com/siddontang/go-snappy/AUTHORS
generated
vendored
Normal file
12
vendor/github.com/siddontang/go-snappy/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# This is the official list of Snappy-Go authors for copyright purposes.
|
||||
# This file is distinct from the CONTRIBUTORS files.
|
||||
# See the latter for an explanation.
|
||||
|
||||
# Names should be added to this file as
|
||||
# Name or Organization <email address>
|
||||
# The email address is not required for organizations.
|
||||
|
||||
# Please keep the list sorted.
|
||||
|
||||
Google Inc.
|
||||
Jan Mercl <0xjnml@gmail.com>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user