diff --git a/Makefile b/Makefile index ace5669e07..b5bdd4114c 100644 --- a/Makefile +++ b/Makefile @@ -155,7 +155,7 @@ test: ## Run tests test-windows: ## Run tests on windows @go clean -testcache - ${OPTS} go test ${TEST_OPTS} ./internal/... ./pkg/... ./cmd/... + ${OPTS} go test ${TEST_OPTS} ./internal/... ./pkg/... ./cmd/skywire-cli... ./cmd/skywire-visor... ./cmd/apps... install-linters: ## Install linters - VERSION=latest ./ci_scripts/install-golangci-lint.sh diff --git a/cmd/skywire-deployment/README.md b/cmd/skywire-deployment/README.md new file mode 100644 index 0000000000..d97bc5e239 --- /dev/null +++ b/cmd/skywire-deployment/README.md @@ -0,0 +1,7 @@ +# Skywire Deployment Documentation + +`../skywire/skywire.go` represents a merged binary which integrates the skywire-cli, skywire-visor, and setup-node. + +The skywire-deployment includes additionally skywire deployment services, service discovery, and the full compliment of dmsg utilities + +Additional documentation to be added. diff --git a/cmd/skywire-deployment/skywire.go b/cmd/skywire-deployment/skywire.go new file mode 100644 index 0000000000..2cad8e66c2 --- /dev/null +++ b/cmd/skywire-deployment/skywire.go @@ -0,0 +1,207 @@ +// cmd/skywire-deployment/skywire-deployment.go +/* +skywire deployment +*/ +package main + +import ( + "fmt" + + cc "github.com/ivanpirog/coloredcobra" + dmsgdisc "github.com/skycoin/dmsg/cmd/dmsg-discovery/commands" + dmsgserver "github.com/skycoin/dmsg/cmd/dmsg-server/commands" + dmsgcurl "github.com/skycoin/dmsg/cmd/dmsgcurl/commands" + dmsghttp "github.com/skycoin/dmsg/cmd/dmsghttp/commands" + dmsgptycli "github.com/skycoin/dmsg/cmd/dmsgpty-cli/commands" + dmsgptyhost "github.com/skycoin/dmsg/cmd/dmsgpty-host/commands" + dmsgptyui "github.com/skycoin/dmsg/cmd/dmsgpty-ui/commands" + dmsgweb "github.com/skycoin/dmsg/cmd/dmsgweb/commands" + sd "github.com/skycoin/skycoin-service-discovery/cmd/service-discovery/commands" + "github.com/spf13/cobra" + + ar "github.com/skycoin/skywire-services/cmd/address-resolver/commands" + confbs "github.com/skycoin/skywire-services/cmd/config-bootstrapper/commands" + dmsgm "github.com/skycoin/skywire-services/cmd/dmsg-monitor/commands" + kg "github.com/skycoin/skywire-services/cmd/keys-gen/commands" + lc "github.com/skycoin/skywire-services/cmd/liveness-checker/commands" + nv "github.com/skycoin/skywire-services/cmd/node-visualizer/commands" + pvm "github.com/skycoin/skywire-services/cmd/public-visor-monitor/commands" + rf "github.com/skycoin/skywire-services/cmd/route-finder/commands" + se "github.com/skycoin/skywire-services/cmd/sw-env/commands" + tpdm "github.com/skycoin/skywire-services/cmd/tpd-monitor/commands" + tpd "github.com/skycoin/skywire-services/cmd/transport-discovery/commands" + tps "github.com/skycoin/skywire-services/cmd/transport-setup/commands" + "github.com/skycoin/skywire-utilities/pkg/buildinfo" + setupnode "github.com/skycoin/skywire/cmd/setup-node/commands" + skywirecli "github.com/skycoin/skywire/cmd/skywire-cli/commands" + "github.com/skycoin/skywire/pkg/visor" +) + +func init() { + dmsgptyCmd.AddCommand( + dmsgptycli.RootCmd, + dmsgptyhost.RootCmd, + dmsgptyui.RootCmd, + ) + dmsgCmd.AddCommand( + dmsgptyCmd, + dmsgdisc.RootCmd, + dmsgserver.RootCmd, + dmsgm.RootCmd, + dmsghttp.RootCmd, + dmsgcurl.RootCmd, + dmsgweb.RootCmd, + ) + svcCmd.AddCommand( + setupnode.RootCmd, + tpd.RootCmd, + tps.RootCmd, + tpdm.RootCmd, + ar.RootCmd, + rf.RootCmd, + confbs.RootCmd, + kg.RootCmd, + lc.RootCmd, + nv.RootCmd, + pvm.RootCmd, + se.RootCmd, + sd.RootCmd, + ) + rootCmd.AddCommand( + visor.RootCmd, + skywirecli.RootCmd, + svcCmd, + dmsgCmd, + ) + visor.RootCmd.Long = ` + ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ ┬ ┬┬┌─┐┌─┐┬─┐ + └─┐├┴┐└┬┘││││├┬┘├┤───└┐┌┘│└─┐│ │├┬┘ + └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘ └┘ ┴└─┘└─┘┴└─` + dmsgcurl.RootCmd.Use = "curl" + dmsgweb.RootCmd.Use = "web" + setupnode.RootCmd.Use = "sn" + + var helpflag bool + rootCmd.SetUsageTemplate(help) + rootCmd.PersistentFlags().BoolVarP(&helpflag, "help", "h", false, "help for "+rootCmd.Use) + rootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) + rootCmd.PersistentFlags().MarkHidden("help") //nolint + rootCmd.CompletionOptions.DisableDefaultCmd = true + rootCmd.SetUsageTemplate(help) + +} + +var rootCmd = &cobra.Command{ + Use: "skywire", + Long: ` + ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ + └─┐├┴┐└┬┘││││├┬┘├┤ + └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘`, + SilenceErrors: true, + SilenceUsage: true, + DisableSuggestions: true, + DisableFlagsInUseLine: true, + Version: buildinfo.Version(), +} + +// RootCmd contains all subcommands +var svcCmd = &cobra.Command{ + Use: "svc", + Short: "Skywire services", + Long: ` + ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ ┌─┐┌─┐┬─┐┬ ┬┬┌─┐┌─┐┌─┐ + └─┐├┴┐└┬┘││││├┬┘├┤───└─┐├┤ ├┬┘└┐┌┘││ ├┤ └─┐ + └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘ └─┘└─┘┴└─ └┘ ┴└─┘└─┘└─┘`, + SilenceErrors: true, + SilenceUsage: true, + DisableSuggestions: true, + DisableFlagsInUseLine: true, + Version: buildinfo.Version(), +} + +// RootCmd contains all binaries which may be separately compiled as subcommands +var dmsgCmd = &cobra.Command{ + Use: "dmsg", + Short: "Dmsg services & utilities", + Long: ` + ┌┬┐┌┬┐┌─┐┌─┐ + │││││└─┐│ ┬ + ─┴┘┴ ┴└─┘└─┘ `, + SilenceErrors: true, + SilenceUsage: true, + DisableSuggestions: true, + DisableFlagsInUseLine: true, +} + +var dmsgptyCmd = &cobra.Command{ + Use: "pty", + Short: "Dmsg pseudoterminal (pty)", + Long: ` + ┌─┐┌┬┐┬ ┬ + ├─┘ │ └┬┘ + ┴ ┴ ┴ `, + SilenceErrors: true, + SilenceUsage: true, + DisableSuggestions: true, + DisableFlagsInUseLine: true, +} + +func main() { + commands := []*cobra.Command{ + dmsgptycli.RootCmd, + dmsgptyhost.RootCmd, + dmsgptyui.RootCmd, + dmsgptyCmd, + dmsgdisc.RootCmd, + dmsgserver.RootCmd, + dmsghttp.RootCmd, + dmsgcurl.RootCmd, + dmsgweb.RootCmd, + dmsgCmd, + tpd.RootCmd, + tps.RootCmd, + tpdm.RootCmd, + ar.RootCmd, + rf.RootCmd, + confbs.RootCmd, + kg.RootCmd, + lc.RootCmd, + nv.RootCmd, + pvm.RootCmd, + se.RootCmd, + dmsgm.RootCmd, + sd.RootCmd, + svcCmd, + setupnode.RootCmd, + visor.RootCmd, + skywirecli.RootCmd, + rootCmd, + } + for _, cmd := range commands { + cc.Init(&cc.Config{ + RootCmd: cmd, + Headings: cc.HiBlue + cc.Bold, + Commands: cc.HiBlue + cc.Bold, + CmdShortDescr: cc.HiBlue, + Example: cc.HiBlue + cc.Italic, + ExecName: cc.HiBlue + cc.Bold, + Flags: cc.HiBlue + cc.Bold, + FlagsDescr: cc.HiBlue, + NoExtraNewlines: true, + NoBottomNewline: true, + }) + } + + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + } +} + +const help = "{{if gt (len .Aliases) 0}}" + + "{{.NameAndAliases}}{{end}}{{if .HasAvailableSubCommands}}" + + "Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand)}}\r\n " + + "{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}\r\n\r\n" + + "Flags:\r\n" + + "{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}\r\n\r\n" + + "Global Flags:\r\n" + + "{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}\r\n\r\n" diff --git a/cmd/skywire/README.md b/cmd/skywire/README.md new file mode 100644 index 0000000000..2c25b4ee29 --- /dev/null +++ b/cmd/skywire/README.md @@ -0,0 +1,2240 @@ +# Skywire Node Documentation + +## Skywire Visor Documentation + + +- [Install](#install) +- [skywire visor usage](#skywire-visor-usage) + - [flags](#skywire-visor-flags) +- [config file generation](#config-file-generation) + + + + +### Install + +```bash +$ cd $GOPATH/src/github.com/skycoin/skywire/cmd/skywire-visor +$ go install ./... +``` + +or + +``` +make install +``` + +### Skywire-visor usage + +After the installation, you can run `skywire visor -h` to see the usage or `skywire visor --all` for advanced usage: +``` +$ skywire visor --help + + +┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ +└─┐├┴┐└┬┘││││├┬┘├┤ +└─┘┴ ┴ ┴ └┴┘┴┴└─└─┘ + +Flags: +-c, --config string config file to use (default): skywire-config.json + --systray run as systray +-i, --hvui run as hypervisor * + --all show all flags +-h, --help help for visor +-v, --version version for visor + + + +$ skywire-visor --all + + ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ + └─┐├┴┐└┬┘││││├┬┘├┤ + └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘ + + Flags: + -c, --config string config file to use (default): skywire-config.json + --dmsg-server string use specified dmsg server public key + -n, --stdin read config from stdin + -p, --pkg use package config /opt/skywire/skywire.json + -u, --user u̶s̶e̶r̶s̶p̶a̶c̶e̶ ̶c̶o̶n̶f̶i̶g̶ does not exist + --systray run as systray + -i, --hvui run as hypervisor * + -x, --nohvui disable hypervisor * + -j, --hv string add remote hypervisor * + -k, --xhv disable remote hypervisors * + -s, --loglvl string [ debug | warn | error | fatal | panic | trace ] * + -q, --pprofmode string [ cpu | mem | mutex | block | trace | http ] + -r, --pprofaddr string pprof http port (default "localhost:6060") + -t, --logtag string logging tag (default "skywire") + -y, --syslog string syslog server address. E.g. localhost:514 + -z, --completion string [ bash | zsh | fish | powershell ] + -l, --storelog store all logs to file + --forcecolor force color logging when out is not STDOUT + -v, --version version for visor + * overrides config file +``` + +#### Skywire visor flags + +Mutually exclusive flags: + +* ` -n, --stdin` +* ` -c, --config` +* ` -p, --pkg` + - requires sudo / root permissions + - only shown when the config file exists +* ` -u, --user` + - requires user permissions + - only shown when the config file exists + +The ` -b, --browser` flag is not available to root / with sudo. + +### Config file generation + +Refer to the [skywire cli documentation](../skywire-cli/README.md) for more detailed information regarding additional flags and argument that may be passed to the following command: + +``` +skywire cli config gen +``` + +With no additional flags or arguments, the configuration is written to skywire-config.json and stdout along with logging to stdout. + + + +## skywire-cli documentation + +skywire command line interface + +* [skywire\-cli documentation](#skywire-cli-documentation) + * [skywire\-cli](#skywire-cli) + * [global flags](#global-flags) + * [subcommand tree](#subcommand-tree) + * [config](#config) + * [config gen](#config-gen) + * [Example for package / msi](#example-for-package--msi) + * [config gen\-keys](#config-gen-keys) + * [config check\-pk](#config-check-pk) + * [config update](#config-update) + * [config update hv](#config-update-hv) + * [config update sc](#config-update-sc) + * [config update ss](#config-update-ss) + * [config update vpnc](#config-update-vpnc) + * [config update vpns](#config-update-vpns) + * [dmsgpty](#dmsgpty) + * [dmsgpty ui](#dmsgpty-ui) + * [dmsgpty url](#dmsgpty-url) + * [dmsgpty list](#dmsgpty-list) + * [dmsgpty start](#dmsgpty-start) + * [visor](#visor) + * [visor app](#visor-app) + * [visor app ls](#visor-app-ls) + * [visor app start](#visor-app-start) + * [visor app stop](#visor-app-stop) + * [visor app register](#visor-app-register) + * [visor app deregister](#visor-app-deregister) + * [visor app log](#visor-app-log) + * [visor app arg](#visor-app-arg) + * [visor app arg autostart](#visor-app-arg-autostart) + * [visor app arg killswitch](#visor-app-arg-killswitch) + * [visor app arg secure](#visor-app-arg-secure) + * [visor app arg passcode](#visor-app-arg-passcode) + * [visor app arg netifc](#visor-app-arg-netifc) + * [visor hv](#visor-hv) + * [visor hv ui](#visor-hv-ui) + * [visor hv cpk](#visor-hv-cpk) + * [visor hv pk](#visor-hv-pk) + * [visor pk](#visor-pk) + * [visor info](#visor-info) + * [visor ver](#visor-ver) + * [visor ports](#visor-ports) + * [visor ip](#visor-ip) + * [visor ping](#visor-ping) + * [visor test](#visor-test) + * [visor start](#visor-start) + * [visor restart](#visor-restart) + * [visor reload](#visor-reload) + * [visor halt](#visor-halt) + * [visor route](#visor-route) + * [visor route ls\-rules](#visor-route-ls-rules) + * [visor route rule](#visor-route-rule) + * [visor route rm\-rule](#visor-route-rm-rule) + * [visor route add\-rule](#visor-route-add-rule) + * [visor route add\-rule app](#visor-route-add-rule-app) + * [visor route add\-rule fwd](#visor-route-add-rule-fwd) + * [visor route add\-rule intfwd](#visor-route-add-rule-intfwd) + * [visor tp](#visor-tp) + * [visor tp type](#visor-tp-type) + * [visor tp ls](#visor-tp-ls) + * [visor tp id](#visor-tp-id) + * [visor tp add](#visor-tp-add) + * [visor tp rm](#visor-tp-rm) + * [visor tp disc](#visor-tp-disc) + * [vpn](#vpn) + * [vpn start](#vpn-start) + * [vpn stop](#vpn-stop) + * [vpn status](#vpn-status) + * [vpn list](#vpn-list) + * [vpn ui](#vpn-ui) + * [vpn url](#vpn-url) + * [ut](#ut) + * [fwd](#fwd) + * [rev](#rev) + * [reward](#reward) + * [survey](#survey) + * [rtfind](#rtfind) + * [mdisc](#mdisc) + * [mdisc entry](#mdisc-entry) + * [mdisc servers](#mdisc-servers) + * [completion](#completion) + * [log](#log) + * [proxy](#proxy) + * [proxy start](#proxy-start) + * [proxy stop](#proxy-stop) + * [proxy status](#proxy-status) + * [proxy list](#proxy-list) + * [tree](#tree) + * [doc](#doc) + + + + +### skywire-cli + +``` + + ┌─┐┬┌─┬ ┬┬ ┬┬┬─┐┌─┐ ┌─┐┬ ┬ + └─┐├┴┐└┬┘││││├┬┘├┤───│ │ │ + └─┘┴ ┴ ┴ └┴┘┴┴└─└─┘ └─┘┴─┘┴ + +Usage: + cli + +Available Commands: + config Generate or update a skywire config + dmsgpty Interact with remote visors + visor Query the Skywire Visor + vpn VPN client + ut query uptime tracker + fwd Control skyforwarding + rev reverse proxy skyfwd + reward skycoin reward address + survey system survey + rtfind Query the Route Finder + mdisc Query remote DMSG Discovery + completion Generate completion script + log survey & transport log collection + proxy Skysocks client + tree subcommand tree + doc generate markdown docs + + +``` + +### global flags + +The skywire-cli interacts with the running visor via rpc calls. By default the rpc server is available on localhost:3435. The rpc address and port the visor is using may be changed in the config file, once generated. + +It is not recommended to expose the rpc server on the local network. Exposing the rpc allows unsecured access to the machine over the local network + +``` + +Global Flags: + + --rpc string RPC server address (default "localhost:3435") + + --json bool print output as json + +``` + +### subcommand tree + +A tree representation of the skywire-cli subcommands + +``` +└─┬cli + ├─┬config + │ ├──gen + │ ├──gen-keys + │ ├──check-pk + │ └─┬update + │ ├──hv + │ ├──sc + │ ├──ss + │ ├──vpnc + │ └──vpns + ├─┬dmsgpty + │ ├──ui + │ ├──url + │ ├──list + │ └──start + ├─┬visor + │ ├─┬app + │ │ ├──ls + │ │ ├──start + │ │ ├──stop + │ │ ├──register + │ │ ├──deregister + │ │ ├──log + │ │ └─┬arg + │ │ ├──autostart + │ │ ├──killswitch + │ │ ├──secure + │ │ ├──passcode + │ │ └──netifc + │ ├─┬hv + │ │ ├──ui + │ │ ├──cpk + │ │ └──pk + │ ├──pk + │ ├──info + │ ├──ver + │ ├──ports + │ ├──ip + │ ├──ping + │ ├──test + │ ├──start + │ ├──restart + │ ├──reload + │ ├──halt + │ ├─┬route + │ │ ├──ls-rules + │ │ ├──rule + │ │ ├──rm-rule + │ │ └─┬add-rule + │ │ ├──app + │ │ ├──fwd + │ │ └──intfwd + │ └─┬tp + │ ├──type + │ ├──ls + │ ├──id + │ ├──add + │ ├──rm + │ └──disc + ├─┬vpn + │ ├──start + │ ├──stop + │ ├──status + │ ├──list + │ ├──ui + │ └──url + ├──ut + ├──fwd + ├──rev + ├──reward + ├──survey + ├──rtfind + ├─┬mdisc + │ ├──entry + │ └──servers + ├──completion + ├──log + ├─┬proxy + │ ├──start + │ ├──stop + │ ├──status + │ └──list + ├──tree + └──doc + + +``` + +#### config + +``` +Generate or update the config file used by skywire-visor. + +Usage: + cli config + +Available Commands: + gen Generate a config file + gen-keys generate public / secret keypair + check-pk check a skywire public key + update Update a config file + + +``` + +##### config gen + +``` +Generate a config file + +Usage: + cli config gen [flags] + +Flags: + -a, --url string services conf url + + (default "http://conf.skywire.skycoin.com") + --loglvl string [ debug | warn | error | fatal | panic | trace | info ] (default "info") + -b, --bestproto best protocol (dmsg | direct) based on location + -c, --noauth disable authentication for hypervisor UI + -d, --dmsghttp use dmsg connection to skywire services + -e, --auth enable auth on hypervisor UI + -f, --force remove pre-existing config + -g, --disableapps string comma separated list of apps to disable + -i, --ishv local hypervisor configuration + -j, --hvpks string list of public keys to add as hypervisor + --dmsgpty string add dmsgpty whitelist PKs + --survey string add survey whitelist PKs + --routesetup string add route setup node PKs + --tpsetup string add transport setup PKs + -k, --os string (linux / mac / win) paths (default "linux") + -l, --publicip allow display node ip in services + -m, --example-apps add example apps to the config + -n, --stdout write config to stdout + -o, --out string output config + -p, --pkg use path for package: /opt/skywire + -u, --user use paths for user space: /root + -r, --regen re-generate existing config & retain keys + -s, --sk cipher.SecKey a random key is generated if unspecified + + (default 0000000000000000000000000000000000000000000000000000000000000000) + -t, --testenv use test deployment conf.skywire.dev + -v, --servevpn enable vpn server + -w, --hide dont print the config to the terminal :: show errors with -n flag + -x, --retainhv retain existing hypervisors with regen + -y, --autoconn disable autoconnect to public visors + -z, --public publicize visor in service discovery + --stcpr int set tcp transport listening port - 0 for random + --sudph int set udp transport listening port - 0 for random + --all show all flags + --binpath string set bin_path + --nofetch do not fetch the services from the service conf url + --nodefaults do not use hardcoded defaults for production / test services + --version string custom version testing override + + +``` + +###### Example for package / msi + +``` +$ skywire cli config gen -bpirxn --version 1.3.0 +{ + "version": "v1.3.7", + "sk": "794ca4760d823e1a190d3aa19487a276944d54e8c1c8d29e16e6fbe6587eb51e", + "pk": "02d3879d36c5d8046a81247388af0fd7caef01884c73f9997ddc362ca96d4ff3d3", + "dmsg": { + "discovery": "http://dmsgd.skywire.skycoin.com", + "sessions_count": 1, + "servers": [] + }, + "dmsgpty": { + "dmsg_port": 22, + "cli_network": "unix", + "cli_address": "/tmp/dmsgpty.sock", + "whitelist": [] + }, + "skywire-tcp": { + "pk_table": null, + "listening_address": ":7777" + }, + "transport": { + "discovery": "http://tpd.skywire.skycoin.com", + "address_resolver": "http://ar.skywire.skycoin.com", + "public_autoconnect": true, + "transport_setup": [ + "03530b786c670fc7f5ab9021478c7ec9cd06a03f3ea1416c50c4a8889ef5bba80e", + "03271c0de223b80400d9bd4b7722b536a245eb6c9c3176781ee41e7bac8f9bad21", + "03a792e6d960c88c6fb2184ee4f16714c58b55f0746840617a19f7dd6e021699d9", + "0313efedc579f57f05d4f5bc3fbf0261f31e51cdcfde7e568169acf92c78868926", + "025c7bbf23e3441a36d7e8a1e9d717921e2a49a2ce035680fec4808a048d244c8a", + "030eb6967f6e23e81db0d214f925fc5ce3371e1b059fb8379ae3eb1edfc95e0b46", + "02e582c0a5e5563aad47f561b272e4c3a9f7ac716258b58e58eb50afd83c286a7f", + "02ddc6c749d6ed067bb68df19c9bcb1a58b7587464043b1707398ffa26a9746b26", + "03aa0b1c4e23616872058c11c6efba777c130a85eaf909945d697399a1eb08426d", + "03adb2c924987d8deef04d02bd95236c5ae172fe5dfe7273e0461d96bf4bc220be" + ], + "log_store": { + "type": "file", + "location": "./local/transport_logs", + "rotation_interval": "168h0m0s" + }, + "stcpr_port": 0, + "sudph_port": 0 + }, + "routing": { + "route_setup_nodes": [ + "0324579f003e6b4048bae2def4365e634d8e0e3054a20fc7af49daf2a179658557" + ], + "route_finder": "http://rf.skywire.skycoin.com", + "route_finder_timeout": "10s", + "min_hops": 0 + }, + "uptime_tracker": { + "addr": "http://ut.skywire.skycoin.com" + }, + "launcher": { + "service_discovery": "http://sd.skycoin.com", + "apps": null, + "server_addr": "localhost:5505", + "bin_path": "./apps", + "display_node_ip": false + }, + "survey_whitelist": [ + "02b5ee5333aa6b7f5fc623b7d5f35f505cb7f974e98a70751cf41962f84c8c4637", + "03714c8bdaee0fb48f47babbc47c33e1880752b6620317c9d56b30f3b0ff58a9c3", + "020d35bbaf0a5abc8ec0ba33cde219fde734c63e7202098e1f9a6cf9daaeee55a9", + "027f7dec979482f418f01dfabddbd750ad036c579a16422125dd9a313eaa59c8e1", + "031d4cf1b7ab4c789b56c769f2888e4a61c778dfa5fe7e5cd0217fc41660b2eb65", + "0327e2cf1d2e516ecbfdbd616a87489cc92a73af97335d5c8c29eafb5d8882264a", + "03abbb3eff140cf3dce468b3fa5a28c80fa02c6703d7b952be6faaf2050990ebf4" + ], + "hypervisors": [], + "cli_addr": "localhost:3435", + "log_level": "", + "local_path": "./local", + "dmsghttp_server_path": "./local/custom", + "stun_servers": [ + "139.162.12.30:3478", + "170.187.228.181:3478", + "172.104.161.184:3478", + "170.187.231.137:3478", + "143.42.74.91:3478", + "170.187.225.78:3478", + "143.42.78.123:3478", + "139.162.12.244:3478" + ], + "shutdown_timeout": "10s", + "restart_check_delay": "1s", + "is_public": false, + "persistent_transports": null +} +``` + +##### config gen-keys + +``` +generate public / secret keypair + +Usage: + cli config gen-keys + + +``` + +##### config check-pk + +``` +check a skywire public key + +Usage: + cli config check-pk + + +``` + +##### config update + +``` +Update a config file + +Usage: + cli config update [flags] + +Available Commands: + hv update hypervisor config + sc update skysocks-client config + ss update skysocks-server config + vpnc update vpn-client config + vpns update vpn-server config + +Flags: + -a, --endpoints update server endpoints + --log-level string level of logging in config + -b, --url string service config URL: conf.skywire.skycoin.com + -t, --testenv use test deployment: conf.skywire.dev + --public-autoconn string change public autoconnect configuration + --set-minhop int change min hops value (default -1) + -i, --input string path of input config file. + -o, --output string config file to output + -p, --pkg update package config /opt/skywire/skywire.json + + +``` + +###### config update hv + +``` +update hypervisor config + +Usage: + cli config update hv [flags] + +Flags: + -+, --add-pks string public keys of hypervisors that should be added to this visor + -r, --reset resets hypervisor configuration + +Global Flags: + -i, --input string path of input config file. + -o, --output string config file to output + -p, --pkg update package config /opt/skywire/skywire.json + + +``` + +###### config update sc + +``` +update skysocks-client config + +Usage: + cli config update sc [flags] + +Flags: + -+, --add-server string add skysocks server address to skysock-client + -r, --reset reset skysocks-client configuration + +Global Flags: + -i, --input string path of input config file. + -o, --output string config file to output + -p, --pkg update package config /opt/skywire/skywire.json + + +``` + +###### config update ss + +``` +update skysocks-server config + +Usage: + cli config update ss [flags] + +Flags: + -s, --passwd string add passcode to skysocks server + -r, --reset reset skysocks configuration + +Global Flags: + -i, --input string path of input config file. + -o, --output string config file to output + -p, --pkg update package config /opt/skywire/skywire.json + + +``` + +###### config update vpnc + +``` +update vpn-client config + +Usage: + cli config update vpnc [flags] + +Flags: + -x, --killsw string change killswitch status of vpn-client + --add-server string add server address to vpn-client + -s, --pass string add passcode of server if needed + -r, --reset reset vpn-client configurations + +Global Flags: + -i, --input string path of input config file. + -o, --output string config file to output + -p, --pkg update package config /opt/skywire/skywire.json + + +``` + +###### config update vpns + +``` +update vpn-server config + +Usage: + cli config update vpns [flags] + +Flags: + -s, --passwd string add passcode to vpn-server + --secure string change secure mode status of vpn-server + --autostart string change autostart of vpn-server + --netifc string set default network interface + -r, --reset reset vpn-server configurations + +Global Flags: + -i, --input string path of input config file. + -o, --output string config file to output + -p, --pkg update package config /opt/skywire/skywire.json + + +``` + +#### dmsgpty + +``` +Interact with remote visors + +Usage: + cli dmsgpty + +Available Commands: + ui Open dmsgpty UI in default browser + url Show dmsgpty UI URL + list List connected visors + start Start dmsgpty session + + +``` + +##### dmsgpty ui + +``` +Open dmsgpty UI in default browser + +Usage: + cli dmsgpty ui [flags] + +Flags: + -i, --input string read from specified config file + -p, --pkg read from /opt/skywire/skywire.json + -v, --visor string public key of visor to connect to + + +``` + +##### dmsgpty url + +``` +Show dmsgpty UI URL + +Usage: + cli dmsgpty url [flags] + +Flags: + -i, --input string read from specified config file + -p, --pkg read from /opt/skywire/skywire.json + -v, --visor string public key of visor to connect to + + +``` + +##### dmsgpty list + +``` +List connected visors + +Usage: + cli dmsgpty list [flags] + +Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### dmsgpty start + +``` +Start dmsgpty session + +Usage: + cli dmsgpty start [flags] + +Flags: + -p, --port string port of remote visor dmsgpty (default "22") + --rpc string RPC server address (default "localhost:3435") + + +``` + +#### visor + +``` +Query the Skywire Visor + +Usage: + cli visor [flags] + +Available Commands: + app App settings + hv Hypervisor + pk Public key of the visor + info Summary of visor info + ver Version and build info + ports List of Ports + ip IP information of network + ping Ping the visor with given pk + test Test the visor with public visors on network + start start visor + restart restart visor + halt Stop a running visor + route View and set rules + tp View and set transports + +Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor app + +``` + + App settings + +Usage: + cli visor app [flags] + +Available Commands: + ls List apps + start Launch app + stop Halt app + register Register app + deregister Deregister app + log Logs from app + arg App args + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor app ls + +``` + + List apps + +Usage: + cli visor app ls [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor app start + +``` + + Launch app + +Usage: + cli visor app start [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor app stop + +``` + + Halt app + +Usage: + cli visor app stop [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor app register + +``` + + Register app + +Usage: + cli visor app register [flags] + +Flags: + -a, --appname string name of the app + -p, --localpath string path of the local folder (default "./local") + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor app deregister + +``` + + Deregister app + +Usage: + cli visor app deregister [flags] + +Flags: + -k, --procKey string proc key of the app to deregister + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor app log + +``` + + Logs from app since RFC3339Nano-formatted timestamp. + + + "beginning" is a special timestamp to fetch all the logs + +Usage: + cli visor app log [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor app arg + +``` +App args + +Usage: + cli visor app arg [flags] + +Available Commands: + autostart Set app autostart + killswitch Set app killswitch + secure Set app secure + passcode Set app passcode + netifc Set app network interface + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +####### visor app arg autostart + +``` +Set app autostart + +Usage: + cli visor app arg autostart (true|false) [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +####### visor app arg killswitch + +``` + + Set app killswitch + +Usage: + cli visor app arg killswitch (true|false) [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +####### visor app arg secure + +``` + + Set app secure + +Usage: + cli visor app arg secure (true|false) [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +####### visor app arg passcode + +``` + + Set app passcode. + + + "remove" is a special arg to remove the passcode + +Usage: + cli visor app arg passcode [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +####### visor app arg netifc + +``` +Set app network interface. + + + "remove" is a special arg to remove the netifc + +Usage: + cli visor app arg netifc [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor hv + +``` + + Hypervisor + + + Access the hypervisor UI + + View remote hypervisor public key + +Usage: + cli visor hv [flags] + +Available Commands: + ui open Hypervisor UI in default browser + cpk Public key of remote hypervisor(s) set in config + pk Public key of remote hypervisor(s) + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor hv ui + +``` + + open Hypervisor UI in default browser + +Usage: + cli visor hv ui [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor hv cpk + +``` + + Public key of remote hypervisor(s) set in config + +Usage: + cli visor hv cpk [flags] + +Flags: + -w, --http serve public key via http + -i, --input string path of input config file. + -p, --pkg read from /opt/skywire/skywire.json + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor hv pk + +``` +Public key of remote hypervisor(s) which are currently connected to + +Usage: + cli visor hv pk [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor pk + +``` + + Public key of the visor + +Usage: + cli visor pk [flags] + +Flags: + -w, --http serve public key via http + -i, --input string path of input config file. + -p, --pkg read from {/opt/skywire/apps /opt/skywire/local {/opt/skywire/users.db %!s(bool=true)}} + -x, --prt string serve public key via http (default "7998") + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor info + +``` + + Summary of visor info + +Usage: + cli visor info [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor ver + +``` + + Version and build info + +Usage: + cli visor ver [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor ports + +``` + + List of all ports used by visor services and apps + +Usage: + cli visor ports [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor ip + +``` + + IP information of network + +Usage: + cli visor ip [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor ping + +``` + + Creates a route with the provided pk as a hop and returns latency on the conn + +Usage: + cli visor ping [flags] + +Flags: + -s, --size int Size of packet, in KB, default is 2KB (default 2) + -t, --tries int Number of tries (default 1) + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor test + +``` + + Creates a route with public visors as a hop and returns latency on the conn + +Usage: + cli visor test [flags] + +Flags: + -c, --count int Count of Public Visors for using in test. (default 2) + -s, --size int Size of packet, in KB, default is 2KB (default 2) + -t, --tries int Number of tries per public visors (default 1) + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor start + +``` +start visor + +Usage: + cli visor start [flags] + +Flags: + -s, --src 'go run' external commands from the skywire sources + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor restart + +``` +restart visor + +Usage: + cli visor restart [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor reload + +``` +reload visor + +Usage: + cli visor reload [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor halt + +``` + + Stop a running visor + +Usage: + cli visor halt [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor route + +``` + + View and set routing rules + +Usage: + cli visor route [flags] + +Available Commands: + ls-rules List routing rules + rule Return routing rule by route ID key + rm-rule Remove routing rule + add-rule Add routing rule + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor route ls-rules + +``` + + List routing rules + +Usage: + cli visor route ls-rules [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor route rule + +``` + + Return routing rule by route ID key + +Usage: + cli visor route rule [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor route rm-rule + +``` + + Remove routing rule + +Usage: + cli visor route rm-rule [flags] + +Flags: + -a, --all remove all routing rules + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor route add-rule + +``` + + Add routing rule + +Usage: + cli visor route add-rule ( app | fwd | intfwd ) [flags] + +Available Commands: + app Add app/consume routing rule + fwd Add forward routing rule + intfwd Add intermediary forward routing rule + +Flags: + --keep-alive duration timeout for rule expiration (default 30s) + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +####### visor route add-rule app + +``` + + Add app/consume routing rule + +Usage: + cli visor route add-rule app \ + \ + \ + \ + \ + \ + || [flags] + +Flags: + -i, --rid string route id + -l, --lpk string local public key + -m, --lpt string local port + -p, --rpk string remote pk + -q, --rpt string remote port + +Global Flags: + --keep-alive duration timeout for rule expiration (default 30s) + --rpc string RPC server address (default "localhost:3435") + + +``` + +####### visor route add-rule fwd + +``` + + Add forward routing rule + +Usage: + cli visor route add-rule fwd \ + \ + \ + \ + \ + \ + \ + \ + || [flags] + +Flags: + -i, --rid string route id + -j, --nrid string next route id + -k, --ntpid string next transport id + -l, --lpk string local public key + -m, --lpt string local port + -p, --rpk string remote pk + -q, --rpt string remote port + +Global Flags: + --keep-alive duration timeout for rule expiration (default 30s) + --rpc string RPC server address (default "localhost:3435") + + +``` + +####### visor route add-rule intfwd + +``` + + Add intermediary forward routing rule + +Usage: + cli visor route add-rule intfwd \ + \ + \ + \ + || [flags] + +Flags: + -i, --rid string route id + -n, --nrid string next route id + -t, --tpid string next transport id + +Global Flags: + --keep-alive duration timeout for rule expiration (default 30s) + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### visor tp + +``` + + Transports are bidirectional communication protocols + used between two Skywire Visors (or Transport Edges) + + Each Transport is represented as a unique 16 byte (128 bit) + UUID value called the Transport ID + and has a Transport Type that identifies + a specific implementation of the Transport. + + Types: stcp stcpr sudph dmsg + +Usage: + cli visor tp [flags] + +Available Commands: + type Transport types used by the local visor + ls Available transports + id Transport summary by id + add Add a transport + rm Remove transport(s) by id + disc Discover remote transport(s) + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor tp type + +``` + + Transport types used by the local visor + +Usage: + cli visor tp type + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor tp ls + +``` + + Available transports + + displays transports of the local visor + +Usage: + cli visor tp ls [flags] + +Flags: + -t, --types strings show transport(s) type(s) comma-separated + -p, --pks strings show transport(s) for public key(s) comma-separated + -l, --logs show transport logs (default true) + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor tp id + +``` + + Transport summary by id + +Usage: + cli visor tp id (-i) + +Flags: + -i, --id string transport ID + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor tp add + +``` + + Add a transport + + If the transport type is unspecified, + the visor will attempt to establish a transport + in the following order: skywire-tcp, stcpr, sudph, dmsg + +Usage: + cli visor tp add (-p) + +Flags: + -r, --rpk string remote public key. + -o, --timeout duration if specified, sets an operation timeout + -t, --type string type of transport to add. + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor tp rm + +``` + + Remove transport(s) by id + +Usage: + cli visor tp rm ( -a || -i ) + +Flags: + -a, --all remove all transports + -i, --id string remove transport of given ID + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +###### visor tp disc + +``` + + Discover remote transport(s) by ID or public key + +Usage: + cli visor tp disc (--id= || --pk=) + +Flags: + -i, --id string obtain transport of given ID + -p, --pk string obtain transports by public key + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +#### vpn + +``` +VPN client + +Usage: + cli vpn [flags] + +Available Commands: + start start the vpn for + stop stop the vpnclient + status vpn client status + list List vpn servers + ui Open VPN UI in default browser + url Show VPN UI URL + +Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### vpn start + +``` +start the vpn for + +Usage: + cli vpn start [flags] + +Flags: + -k, --pk string server public key + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### vpn stop + +``` +stop the vpnclient + +Usage: + cli vpn stop [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### vpn status + +``` +vpn client status + +Usage: + cli vpn status [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### vpn list + +``` +List vpn servers from service discovery + http://sd.skycoin.com/api/services?type=vpn + http://sd.skycoin.com/api/services?type=vpn&country=US + +Usage: + cli vpn list [flags] + +Flags: + -c, --country string filter results by country + -b, --direct query service discovery directly + -n, --num int number of results to return + -k, --pk string check vpn service discovery for public key + -s, --stats return only a count of the results + -u, --unfilter provide unfiltered results + -a, --url string service discovery url default: + http://sd.skycoin.com + -v, --ver string filter results by version (default "v1.3.7-42-gf9e3cc38") + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### vpn ui + +``` +Open VPN UI in default browser + +Usage: + cli vpn ui [flags] + +Flags: + -c, --config string config path + -p, --pkg use package config path: /opt/skywire + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### vpn url + +``` +Show VPN UI URL + +Usage: + cli vpn url [flags] + +Flags: + -c, --config string config path + -p, --pkg use package config path: /opt/skywire + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +#### ut + +``` +query uptime tracker + Check local visor daily uptime percent with: + skywire cli ut -k $(skywire cli visor pk) + +Usage: + cli ut [flags] + +Flags: + -n, --min int list visors meeting minimum uptime (default 75) + -o, --on list currently online visors + -k, --pk string check uptime for the specified key + -s, --stats count the number of results + -u, --url string specify alternative uptime tracker url + default: http://ut.skywire.skycoin.com/uptimes?v=v2 + + +``` + +#### fwd + +``` +Control skyforwarding + forward local ports over skywire + +Usage: + cli fwd [flags] + +Flags: + -d, --deregister deregister local port of the external (http) app + -l, --ls list registered local ports + -p, --port int local port of the external (http) app + + +``` + +#### rev + +``` +connect or disconnect from remote ports + +Usage: + cli rev [flags] + +Flags: + -l, --ls list configured connections + -k, --pk string remote public key to connect to + -p, --port int local port to reverse proxy + -r, --remote int remote port to read from + -d, --stop string disconnect from specified + + +``` + +#### reward + +``` + + skycoin reward address set to: + +Usage: + cli reward
|| [flags] + +Flags: + --all show all flags + + +``` + +#### survey + +``` +print the system survey + +Usage: + cli survey + +Flags: + -s, --sha generate checksum of system survey + + +``` + +``` +{ + "timestamp": "2023-05-26T12:39:16.93648714-05:00", + "public_key": "000000000000000000000000000000000000000000000000000000000000000000", + "go_os": "linux", + "go_arch": "amd64", + "zcalusic_sysinfo": { + "sysinfo": { + "version": "0.9.5", + "timestamp": "2023-05-26T12:39:15.695232998-05:00" + }, + "node": { + "hostname": "node", + "machineid": "0de8bf5b7b2d4637ac44c3de851fb93c", + "timezone": "America/Chicago" + }, + "os": { + "name": "EndeavourOS", + "vendor": "endeavouros", + "architecture": "amd64" + }, + "kernel": { + "release": "6.3.1-arch2-1", + "version": "#1 SMP PREEMPT_DYNAMIC Wed, 10 May 2023 08:54:47 +0000", + "architecture": "x86_64" + }, + "product": { + "name": "OptiPlex 7010", + "vendor": "Dell Inc.", + "version": "01", + "serial": "C060HX1" + }, + "board": { + "name": "0MN1TX", + "vendor": "Dell Inc.", + "version": "A00", + "serial": "/C060HX1/CN7220035300C5/" + }, + "chassis": { + "type": 16, + "vendor": "Dell Inc.", + "serial": "C060HX1" + }, + "bios": { + "vendor": "Dell Inc.", + "version": "A25", + "date": "05/10/2017" + }, + "cpu": { + "vendor": "GenuineIntel", + "model": "Intel(R) Core(TM) i7-3770S CPU @ 3.10GHz", + "speed": 3100, + "cache": 8192, + "cpus": 1, + "cores": 4, + "threads": 8 + }, + "memory": { + "type": "DDR3", + "speed": 1600, + "size": 16384 + }, + "storage": [ + { + "name": "sda", + "driver": "sd", + "vendor": "ATA", + "model": "JAJS600M128C", + "serial": "30040655357", + "size": 128 + } + ], + "network": [ + { + "name": "eno1", + "driver": "e1000e", + "macaddress": "b8:ca:3a:8c:70:23", + "port": "tp", + "speed": 1000 + } + ] + }, + "ip.skycoin.com": { + "ip_address": "70.121.6.231", + "latitude": 33.1371, + "longitude": -96.7488, + "postal_code": "75035", + "continent_code": "NA", + "country_code": "US", + "country_name": "United States", + "region_code": "TX", + "region_name": "Texas", + "province_code": "", + "province_name": "", + "city_name": "Frisco", + "timezone": "America/Chicago" + }, + "ip_addr": [ + { + "ifindex": 1, + "ifname": "lo", + "flags": [ + "LOOPBACK", + "UP", + "LOWER_UP" + ], + "mtu": 65536, + "qdisc": "noqueue", + "operstate": "UNKNOWN", + "group": "default", + "txqlen": 1000, + "link_type": "loopback", + "address": "00:00:00:00:00:00", + "broadcast": "00:00:00:00:00:00", + "addr_info": [ + { + "family": "inet", + "local": "127.0.0.1", + "prefixlen": 8, + "scope": "host", + "label": "lo", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + }, + { + "family": "inet6", + "local": "::1", + "prefixlen": 128, + "scope": "host", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + }, + { + "ifindex": 2, + "ifname": "eno1", + "flags": [ + "BROADCAST", + "MULTICAST", + "UP", + "LOWER_UP" + ], + "mtu": 1500, + "qdisc": "fq_codel", + "operstate": "UP", + "group": "default", + "txqlen": 1000, + "link_type": "ether", + "address": "b8:ca:3a:8c:70:23", + "broadcast": "ff:ff:ff:ff:ff:ff", + "addr_info": [ + { + "family": "inet", + "local": "192.168.1.57", + "prefixlen": 24, + "scope": "global", + "label": "eno1", + "valid_life_time": 75286, + "preferred_life_time": 75286 + }, + { + "family": "inet6", + "local": "fe80::419b:25f0:b69a:b34c", + "prefixlen": 64, + "scope": "link", + "valid_life_time": 4294967295, + "preferred_life_time": 4294967295 + } + ] + } + ], + "ghw_blockinfo": { + "total_size_bytes": 128035676160, + "disks": [ + { + "name": "sda", + "size_bytes": 128035676160, + "physical_block_size_bytes": 512, + "drive_type": "ssd", + "removable": false, + "storage_controller": "scsi", + "bus_path": "pci-0000:00:1f.2-ata-1.0", + "vendor": "ATA", + "model": "JAJS600M128C", + "serial_number": "30040655357", + "wwn": "0x5000000000003244", + "partitions": [ + { + "name": "sda1", + "label": "unknown", + "mount_point": "/", + "size_bytes": 128033659904, + "type": "ext4", + "read_only": false, + "uuid": "514fad51-01", + "filesystem_label": "unknown" + } + ] + } + ] + }, + "ghw_productinfo": { + "family": "", + "name": "OptiPlex 7010", + "vendor": "Dell Inc.", + "serial_number": "C060HX1", + "uuid": "4c4c4544-0030-3610-8030-c3c04f485831", + "sku": "OptiPlex 7010", + "version": "01" + }, + "ghw_memoryinfo": { + "total_physical_bytes": 17179869184, + "total_usable_bytes": 16655327232, + "supported_page_sizes": [ + 2097152 + ], + "modules": null + }, + "uuid": "99246216-8786-4332-a8e2-b1bb15e68574", + "skywire_version": "fatal: detected dubious ownership in repository at '/home/d0mo/go/src/github.com/0pcom/skywire'\nTo add an exception for this directory, call:\n\n\tgit config --global --add safe.directory /home/d0mo/go/src/github.com/0pcom/skywire\n" +} +``` + +#### rtfind + +``` +Query the Route Finder +Assumes the local visor public key as an argument if only one argument is given + +Usage: + cli rtfind | [flags] + +Flags: + -n, --min uint16 minimum hops (default 1) + -x, --max uint16 maximum hops (default 1000) + -t, --timeout duration request timeout (default 10s) + -a, --addr string route finder service address + http://rf.skywire.skycoin.com + + +``` + +#### mdisc + +``` +Query remote DMSG Discovery + +Usage: + cli mdisc + +Available Commands: + entry Fetch an entry + servers Fetch available servers + + +``` + +##### mdisc entry + +``` +Fetch an entry + +Usage: + cli mdisc entry [flags] + +Flags: + -a, --addr string DMSG discovery server address + http://dmsgd.skywire.skycoin.com + + +``` + +##### mdisc servers + +``` +Fetch available servers + +Usage: + cli mdisc servers [flags] + +Flags: + --addr string address of DMSG discovery server + (default "http://dmsgd.skywire.skycoin.com") + + +``` + +#### completion + +``` +Generate completion script + +Usage: + cli completion [bash|zsh|fish|powershell] + + +``` + +#### log + +``` +collect surveys and transport logging from visors which are online in the uptime tracker + +Usage: + cli log [flags] + +Flags: + -e, --env string selecting env to fetch uptimes, default is prod (default "prod") + -l, --log fetch only transport logs + -v, --survey fetch only surveys + -c, --clean delete files and folders on errors + --minv string minimum version for get logs, default is 1.3.4 (default "v1.3.4") + -n, --duration int numberof days before today to fetch transport logs for (default 1) + --all consider all visors ; no version filtering + --batchSize int number of visor in each batch, default is 50 (default 50) + --maxfilesize int maximum file size allowed to download during collecting logs, in KB (default 30) + -D, --dmsg-disc string dmsg discovery url + (default "http://dmsgd.skywire.skycoin.com") + -u, --ut string custom uptime tracker url + -s, --sk cipher.SecKey a random key is generated if unspecified + + (default 0000000000000000000000000000000000000000000000000000000000000000) + + +``` + +#### proxy + +``` +Skysocks client + +Usage: + cli proxy [flags] + +Available Commands: + start start the proxy client + stop stop the proxy client + status proxy client status + list List servers + +Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### proxy start + +``` +start the proxy client + +Usage: + cli proxy start [flags] + +Flags: + -k, --pk string server public key + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### proxy stop + +``` +stop the proxy client + +Usage: + cli proxy stop [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### proxy status + +``` +proxy client status + +Usage: + cli proxy status [flags] + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +##### proxy list + +``` +List proxy servers from service discovery + http://sd.skycoin.com/api/services?type=proxy + http://sd.skycoin.com/api/services?type=proxy&country=US + +Usage: + cli proxy list [flags] + +Flags: + -c, --country string filter results by country + -b, --direct query service discovery directly + -n, --num int number of results to return (0 = all) + -k, --pk string check proxy service discovery for public key + -s, --stats return only a count of the results + -u, --unfilter provide unfiltered results + -a, --url string service discovery url default: + http://sd.skycoin.com + -v, --ver string filter results by version (default "v1.3.7-42-gf9e3cc38") + +Global Flags: + --rpc string RPC server address (default "localhost:3435") + + +``` + +#### tree + +``` +subcommand tree + +Usage: + cli tree + + +``` + +#### doc + +``` +generate markdown docs + + UNHIDEFLAGS=1 go run cmd/skywire-cli/skywire-cli.go doc + + UNHIDEFLAGS=1 go run cmd/skywire-cli/skywire-cli.go doc > cmd/skywire-cli/README1.md + + generate toc: + + cat cmd/skywire-cli/README1.md | gh-md-toc + +Usage: + cli doc + + +``` diff --git a/go.mod b/go.mod index 371f8705c6..5e20e53420 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/skycoin/skywire go 1.21 -toolchain go1.21.3 +toolchain go1.21.5 require ( github.com/AudriusButkevicius/pfilter v0.0.11 @@ -29,6 +29,8 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/skycoin/dmsg v1.3.14 github.com/skycoin/skycoin v0.27.1 + github.com/skycoin/skycoin-service-discovery v0.0.0-20231221001759-d1af6ec27db1 + github.com/skycoin/skywire-services v0.0.0-20231221001820-3212895ddf12 github.com/skycoin/skywire-utilities v1.3.14 github.com/skycoin/systray v1.10.0 github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 @@ -43,7 +45,7 @@ require ( golang.org/x/net v0.14.0 golang.org/x/sync v0.3.0 golang.org/x/sys v0.11.0 - golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675 + golang.zx2c4.com/wireguard v0.0.20200320 ) require ( @@ -55,30 +57,55 @@ require ( github.com/Microsoft/go-winio v0.6.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/bytedance/sonic v1.10.0 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/creack/pty v1.1.18 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/badger/v3 v3.2103.2 // indirect + github.com/dgraph-io/ristretto v0.1.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.0 // indirect github.com/fatih/color v1.15.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-chi/chi v4.1.2+incompatible // indirect + github.com/go-chi/cors v1.2.1 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.15.1 // indirect + github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/google/flatbuffers v1.12.1 // indirect github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f // indirect github.com/gookit/color v1.5.4 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/gojq v0.12.13 // indirect github.com/itchyny/timefmt-go v0.1.5 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.12.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.11.0 // indirect + github.com/jackc/pgx/v4 v4.16.1 // indirect github.com/jaypipes/pcidb v1.0.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect @@ -94,11 +121,13 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/onsi/ginkgo/v2 v2.12.0 // indirect github.com/pelletier/go-toml/v2 v2.0.9 // indirect + github.com/pires/go-proxyproto v0.6.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qtls-go1-20 v0.3.3 // indirect github.com/quic-go/quic-go v0.38.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect + github.com/rs/cors v1.8.2 // indirect github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6 // indirect github.com/stretchr/objx v0.5.1 // indirect github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect @@ -110,6 +139,7 @@ require ( github.com/valyala/histogram v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect + go.opencensus.io v0.23.0 // indirect golang.org/x/arch v0.4.0 // indirect golang.org/x/crypto v0.12.0 // indirect golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect @@ -117,11 +147,12 @@ require ( golang.org/x/term v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect - golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.3.8 // indirect + gorm.io/gorm v1.23.8 // indirect howett.net/plist v1.0.0 // indirect mvdan.cc/sh/v3 v3.7.0 // indirect nhooyr.io/websocket v1.8.7 // indirect diff --git a/go.sum b/go.sum index a63dc333b3..14258f12a6 100644 --- a/go.sum +++ b/go.sum @@ -24,12 +24,17 @@ github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/ github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/metrics v1.24.0 h1:ILavebReOjYctAGY5QU2F9X0MYvkcrG3aEn2RKa1Zkw= github.com/VictoriaMetrics/metrics v1.24.0/go.mod h1:eFT25kvsTidQFHb6U0oa0rTrDRdz4xTYjpL8+UPohys= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= @@ -44,6 +49,11 @@ github.com/bytedance/sonic v1.10.0/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf5 github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029 h1:POmUHfxXdeyM8Aomg4tKDcwATCFuW+cYLkj6pwsw9pc= github.com/ccding/go-stun/stun v0.0.0-20200514191101-4dc67bcdb029/go.mod h1:Rpr5n9cGHYdM3S3IK8ROSUUUYjQOu+MSUCZDcJbYWi8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -52,17 +62,37 @@ github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v3 v3.2103.2 h1:dpyM5eCJAtQCBcMCZcT4UBZchuTJgCywerHHgmxfxM8= +github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= +github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -71,6 +101,9 @@ github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBD github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA= github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gen2brain/dlgs v0.0.0-20220603100644-40c77870fa8d h1:dHYKX8CBAs1zSGXm3q3M15CLAEwPEkwrK1ed8FCo+Xo= @@ -82,8 +115,14 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -101,6 +140,11 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.15.1 h1:BSe8uhN+xQ4r5guV/ywQI4gO59C2raYcGffYWZEjZzM= github.com/go-playground/validator/v10 v10.15.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= +github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -118,11 +162,21 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= @@ -131,16 +185,22 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -148,7 +208,9 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f h1:pDhu5sgp8yJlEF/g6osliIIpF9K4F5jvkULXa4daRDQ= github.com/google/pprof v0.0.0-20230821062121-407c9e7a662f/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= @@ -163,6 +225,7 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -175,6 +238,53 @@ github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/ivanpirog/coloredcobra v1.0.1 h1:aURSdEmlR90/tSiWS0dMjdwOvCVUeYLfltLfbgNxrN4= github.com/ivanpirog/coloredcobra v1.0.1/go.mod h1:iho4nEKcnwZFiniGSdcgdvRgZNjxm+h20acv8vqmN6Q= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8= +github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= +github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs= +github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y= +github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/james-barrow/golang-ipc v1.2.4 h1:d4NXRQxq6OWviWU8uAaob8R0YZGy/PhAkXGLpBNpkA4= github.com/james-barrow/golang-ipc v1.2.4/go.mod h1:+egiWSbOWmiPucFGSl4GNB1YSzrVGehyl7/7pW4N8F0= github.com/jaypipes/ghw v0.12.0 h1:xU2/MDJfWmBhJnujHY9qwXQLs3DBsf0/Xa9vECY0Tho= @@ -182,10 +292,18 @@ github.com/jaypipes/ghw v0.12.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeS github.com/jaypipes/pcidb v1.0.0 h1:vtZIfkiCUE42oYbJS0TAq9XSfSmcsgo9IdxSm9qzYU8= github.com/jaypipes/pcidb v1.0.0/go.mod h1:TnYUvqhPBzCKnH34KrIX22kAeEbDCSRJ9cqLRCuNDfk= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= @@ -196,25 +314,37 @@ github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -229,21 +359,30 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= github.com/onsi/ginkgo/v2 v2.12.0/go.mod h1:ZNEzXISYlqpb8S36iN71ifqLi3vVD1rVJGvWRCJOUpQ= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/orandin/lumberjackrus v1.0.1 h1:7ysDQ0MHD79zIFN9/EiDHjUcgopNi5ehtxFDy8rUkWo= github.com/orandin/lumberjackrus v1.0.1/go.mod h1:xYLt6H8W93pKnQgUQaxsApS0Eb4BwHLOkxk5DVzf5H0= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0= github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= +github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -265,13 +404,26 @@ github.com/quic-go/quic-go v0.38.1/go.mod h1:ijnZM7JsFIkp4cRyjxJNIzdSfCLmUMg9wdy github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97 h1:3RPlVWzZ/PDqmVuf/FKHARG5EMid/tl7cv54Sw/QRVY= github.com/rogpeppe/go-internal v1.10.1-0.20230524175051-ec119421bb97/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -281,24 +433,41 @@ github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6 h1:1Nc5EBY6pjfw1kwW0 github.com/skycoin/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:UXghlricA7J3aRD/k7p/zBObQfmBawwCxIVPVjz2Q3o= github.com/skycoin/skycoin v0.27.1 h1:HatxsRwVSPaV4qxH6290xPBmkH/HgiuAoY2qC+e8C9I= github.com/skycoin/skycoin v0.27.1/go.mod h1:78nHjQzd8KG0jJJVL/j0xMmrihXi70ti63fh8vXScJw= +github.com/skycoin/skycoin-service-discovery v0.0.0-20231221001759-d1af6ec27db1 h1:CQpzVCMiAZfL3NsByipzzPDIzBRJA00AURU/9Pctq1E= +github.com/skycoin/skycoin-service-discovery v0.0.0-20231221001759-d1af6ec27db1/go.mod h1:6VwZDwW4aO6fucuZKiJQ4PnnVYi5CfN/eHcK8F0C05M= +github.com/skycoin/skywire-services v0.0.0-20231221001820-3212895ddf12 h1:fic0WKltARs0JnbNFcKesijNgcPOeJh7CsFs/KLZoJw= +github.com/skycoin/skywire-services v0.0.0-20231221001820-3212895ddf12/go.mod h1:HYqBsmgat3wTk1zwGLNj3q5N3iCpJOwYOuLexqKH02k= github.com/skycoin/skywire-utilities v1.3.14 h1:AzTV3oiij7b2VgpiZHJj/oy4Tojf22I+r50Riza8Xt0= github.com/skycoin/skywire-utilities v1.3.14/go.mod h1:yFKWpL1bDRPKU3uK+cTF4PnYUMe+eyIj5N2bk4sF5Cw= github.com/skycoin/systray v1.10.0 h1:fQZJHMylpVvfmOOTLvUssfyHVDoC8Idx6Ba2BlLEuGg= github.com/skycoin/systray v1.10.0/go.mod h1:/i17Eni5GxFiboIZceeamY5LktDSFFRCvd3fBMerQ+4= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -320,6 +489,7 @@ github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= @@ -330,23 +500,49 @@ github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tz github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zcalusic/sysinfo v1.0.1 h1:cVh8q3codjh43AGRTa54dJ2Zq+qPejv8n2VWpxKViwc= github.com/zcalusic/sysinfo v1.0.1/go.mod h1:LxwKwtQdbTIQc65drhjQzYzt0o7jfB80LrrZm7SWn8o= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= @@ -356,6 +552,11 @@ golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMe golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -368,7 +569,12 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -379,7 +585,10 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -387,11 +596,20 @@ golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -414,6 +632,7 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -426,58 +645,79 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E= golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= -golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= -golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675 h1:/J/RVnr7ng4fWPRH3xa4WtBJ1Jp+Auu4YNLmGiPv5QU= -golang.zx2c4.com/wireguard v0.0.0-20230223181233-21636207a675/go.mod h1:whfbyDBt09xhCYQWtO2+3UVjlaq6/9hDZrjg2ZE6SyA= -golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1 h1:EY138uSo1JYlDq+97u1FtcOUwPpIU6WL1Lkt7WpYjPA= -golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1/go.mod h1:tqur9LnfstdR9ep2LaJT4lFUl0EjlHtge+gAjmsHUG4= +golang.zx2c4.com/wireguard v0.0.20200320 h1:1vE6zVeO7fix9cJX1Z9ZQ+ikPIIx7vIyU0o0tLDD88g= +golang.zx2c4.com/wireguard v0.0.20200320/go.mod h1:lDian4Sw4poJ04SgHh35nzMVwGSYlPumkdnHcucAQoY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -488,10 +728,14 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0 h1:Wobr37noukisGxpKo5jAsLREcpj61RxrWYzD8uwveOY= -gvisor.dev/gvisor v0.0.0-20221203005347-703fd9b7fbc0/go.mod h1:Dn5idtptoW1dIos9U6A2rpebLs/MtTwFacjKb8jLdQA= +gorm.io/driver/postgres v1.3.8 h1:8bEphSAB69t3odsCR4NDzt581iZEWQuRM27Cg6KgfPY= +gorm.io/driver/postgres v1.3.8/go.mod h1:qB98Aj6AhRO/oyu/jmZsi/YM9g6UzVCjMxO/6frFvcA= +gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE= +gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0= diff --git a/vendor/github.com/cespare/xxhash/LICENSE.txt b/vendor/github.com/cespare/xxhash/LICENSE.txt new file mode 100644 index 0000000000..24b53065f4 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2016 Caleb Spare + +MIT License + +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. diff --git a/vendor/github.com/cespare/xxhash/README.md b/vendor/github.com/cespare/xxhash/README.md new file mode 100644 index 0000000000..0982fd25e5 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/README.md @@ -0,0 +1,50 @@ +# xxhash + +[![GoDoc](https://godoc.org/github.com/cespare/xxhash?status.svg)](https://godoc.org/github.com/cespare/xxhash) + +xxhash is a Go implementation of the 64-bit +[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a +high-quality hashing algorithm that is much faster than anything in the Go +standard library. + +The API is very small, taking its cue from the other hashing packages in the +standard library: + + $ go doc github.com/cespare/xxhash ! + package xxhash // import "github.com/cespare/xxhash" + + Package xxhash implements the 64-bit variant of xxHash (XXH64) as described + at http://cyan4973.github.io/xxHash/. + + func New() hash.Hash64 + func Sum64(b []byte) uint64 + func Sum64String(s string) uint64 + +This implementation provides a fast pure-Go implementation and an even faster +assembly implementation for amd64. + +## Benchmarks + +Here are some quick benchmarks comparing the pure-Go and assembly +implementations of Sum64 against another popular Go XXH64 implementation, +[github.com/OneOfOne/xxhash](https://github.com/OneOfOne/xxhash): + +| input size | OneOfOne | cespare (purego) | cespare | +| --- | --- | --- | --- | +| 5 B | 416 MB/s | 720 MB/s | 872 MB/s | +| 100 B | 3980 MB/s | 5013 MB/s | 5252 MB/s | +| 4 KB | 12727 MB/s | 12999 MB/s | 13026 MB/s | +| 10 MB | 9879 MB/s | 10775 MB/s | 10913 MB/s | + +These numbers were generated with: + +``` +$ go test -benchtime 10s -bench '/OneOfOne,' +$ go test -tags purego -benchtime 10s -bench '/xxhash,' +$ go test -benchtime 10s -bench '/xxhash,' +``` + +## Projects using this package + +- [InfluxDB](https://github.com/influxdata/influxdb) +- [Prometheus](https://github.com/prometheus/prometheus) diff --git a/vendor/github.com/cespare/xxhash/rotate.go b/vendor/github.com/cespare/xxhash/rotate.go new file mode 100644 index 0000000000..f3eac5ebc0 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/rotate.go @@ -0,0 +1,14 @@ +// +build !go1.9 + +package xxhash + +// TODO(caleb): After Go 1.10 comes out, remove this fallback code. + +func rol1(x uint64) uint64 { return (x << 1) | (x >> (64 - 1)) } +func rol7(x uint64) uint64 { return (x << 7) | (x >> (64 - 7)) } +func rol11(x uint64) uint64 { return (x << 11) | (x >> (64 - 11)) } +func rol12(x uint64) uint64 { return (x << 12) | (x >> (64 - 12)) } +func rol18(x uint64) uint64 { return (x << 18) | (x >> (64 - 18)) } +func rol23(x uint64) uint64 { return (x << 23) | (x >> (64 - 23)) } +func rol27(x uint64) uint64 { return (x << 27) | (x >> (64 - 27)) } +func rol31(x uint64) uint64 { return (x << 31) | (x >> (64 - 31)) } diff --git a/vendor/github.com/cespare/xxhash/rotate19.go b/vendor/github.com/cespare/xxhash/rotate19.go new file mode 100644 index 0000000000..b99612bab8 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/rotate19.go @@ -0,0 +1,14 @@ +// +build go1.9 + +package xxhash + +import "math/bits" + +func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) } +func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) } +func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) } +func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) } +func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) } +func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) } +func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) } +func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) } diff --git a/vendor/github.com/cespare/xxhash/v2/LICENSE.txt b/vendor/github.com/cespare/xxhash/v2/LICENSE.txt new file mode 100644 index 0000000000..24b53065f4 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/v2/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2016 Caleb Spare + +MIT License + +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. diff --git a/vendor/github.com/cespare/xxhash/v2/README.md b/vendor/github.com/cespare/xxhash/v2/README.md new file mode 100644 index 0000000000..792b4a60b3 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/v2/README.md @@ -0,0 +1,69 @@ +# xxhash + +[![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2) +[![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml) + +xxhash is a Go implementation of the 64-bit +[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a +high-quality hashing algorithm that is much faster than anything in the Go +standard library. + +This package provides a straightforward API: + +``` +func Sum64(b []byte) uint64 +func Sum64String(s string) uint64 +type Digest struct{ ... } + func New() *Digest +``` + +The `Digest` type implements hash.Hash64. Its key methods are: + +``` +func (*Digest) Write([]byte) (int, error) +func (*Digest) WriteString(string) (int, error) +func (*Digest) Sum64() uint64 +``` + +This implementation provides a fast pure-Go implementation and an even faster +assembly implementation for amd64. + +## Compatibility + +This package is in a module and the latest code is in version 2 of the module. +You need a version of Go with at least "minimal module compatibility" to use +github.com/cespare/xxhash/v2: + +* 1.9.7+ for Go 1.9 +* 1.10.3+ for Go 1.10 +* Go 1.11 or later + +I recommend using the latest release of Go. + +## Benchmarks + +Here are some quick benchmarks comparing the pure-Go and assembly +implementations of Sum64. + +| input size | purego | asm | +| --- | --- | --- | +| 5 B | 979.66 MB/s | 1291.17 MB/s | +| 100 B | 7475.26 MB/s | 7973.40 MB/s | +| 4 KB | 17573.46 MB/s | 17602.65 MB/s | +| 10 MB | 17131.46 MB/s | 17142.16 MB/s | + +These numbers were generated on Ubuntu 18.04 with an Intel i7-8700K CPU using +the following commands under Go 1.11.2: + +``` +$ go test -tags purego -benchtime 10s -bench '/xxhash,direct,bytes' +$ go test -benchtime 10s -bench '/xxhash,direct,bytes' +``` + +## Projects using this package + +- [InfluxDB](https://github.com/influxdata/influxdb) +- [Prometheus](https://github.com/prometheus/prometheus) +- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) +- [FreeCache](https://github.com/coocood/freecache) +- [FastCache](https://github.com/VictoriaMetrics/fastcache) diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash.go b/vendor/github.com/cespare/xxhash/v2/xxhash.go new file mode 100644 index 0000000000..15c835d541 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/v2/xxhash.go @@ -0,0 +1,235 @@ +// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described +// at http://cyan4973.github.io/xxHash/. +package xxhash + +import ( + "encoding/binary" + "errors" + "math/bits" +) + +const ( + prime1 uint64 = 11400714785074694791 + prime2 uint64 = 14029467366897019727 + prime3 uint64 = 1609587929392839161 + prime4 uint64 = 9650029242287828579 + prime5 uint64 = 2870177450012600261 +) + +// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where +// possible in the Go code is worth a small (but measurable) performance boost +// by avoiding some MOVQs. Vars are needed for the asm and also are useful for +// convenience in the Go code in a few places where we need to intentionally +// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the +// result overflows a uint64). +var ( + prime1v = prime1 + prime2v = prime2 + prime3v = prime3 + prime4v = prime4 + prime5v = prime5 +) + +// Digest implements hash.Hash64. +type Digest struct { + v1 uint64 + v2 uint64 + v3 uint64 + v4 uint64 + total uint64 + mem [32]byte + n int // how much of mem is used +} + +// New creates a new Digest that computes the 64-bit xxHash algorithm. +func New() *Digest { + var d Digest + d.Reset() + return &d +} + +// Reset clears the Digest's state so that it can be reused. +func (d *Digest) Reset() { + d.v1 = prime1v + prime2 + d.v2 = prime2 + d.v3 = 0 + d.v4 = -prime1v + d.total = 0 + d.n = 0 +} + +// Size always returns 8 bytes. +func (d *Digest) Size() int { return 8 } + +// BlockSize always returns 32 bytes. +func (d *Digest) BlockSize() int { return 32 } + +// Write adds more data to d. It always returns len(b), nil. +func (d *Digest) Write(b []byte) (n int, err error) { + n = len(b) + d.total += uint64(n) + + if d.n+n < 32 { + // This new data doesn't even fill the current block. + copy(d.mem[d.n:], b) + d.n += n + return + } + + if d.n > 0 { + // Finish off the partial block. + copy(d.mem[d.n:], b) + d.v1 = round(d.v1, u64(d.mem[0:8])) + d.v2 = round(d.v2, u64(d.mem[8:16])) + d.v3 = round(d.v3, u64(d.mem[16:24])) + d.v4 = round(d.v4, u64(d.mem[24:32])) + b = b[32-d.n:] + d.n = 0 + } + + if len(b) >= 32 { + // One or more full blocks left. + nw := writeBlocks(d, b) + b = b[nw:] + } + + // Store any remaining partial block. + copy(d.mem[:], b) + d.n = len(b) + + return +} + +// Sum appends the current hash to b and returns the resulting slice. +func (d *Digest) Sum(b []byte) []byte { + s := d.Sum64() + return append( + b, + byte(s>>56), + byte(s>>48), + byte(s>>40), + byte(s>>32), + byte(s>>24), + byte(s>>16), + byte(s>>8), + byte(s), + ) +} + +// Sum64 returns the current hash. +func (d *Digest) Sum64() uint64 { + var h uint64 + + if d.total >= 32 { + v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4 + h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4) + h = mergeRound(h, v1) + h = mergeRound(h, v2) + h = mergeRound(h, v3) + h = mergeRound(h, v4) + } else { + h = d.v3 + prime5 + } + + h += d.total + + i, end := 0, d.n + for ; i+8 <= end; i += 8 { + k1 := round(0, u64(d.mem[i:i+8])) + h ^= k1 + h = rol27(h)*prime1 + prime4 + } + if i+4 <= end { + h ^= uint64(u32(d.mem[i:i+4])) * prime1 + h = rol23(h)*prime2 + prime3 + i += 4 + } + for i < end { + h ^= uint64(d.mem[i]) * prime5 + h = rol11(h) * prime1 + i++ + } + + h ^= h >> 33 + h *= prime2 + h ^= h >> 29 + h *= prime3 + h ^= h >> 32 + + return h +} + +const ( + magic = "xxh\x06" + marshaledSize = len(magic) + 8*5 + 32 +) + +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (d *Digest) MarshalBinary() ([]byte, error) { + b := make([]byte, 0, marshaledSize) + b = append(b, magic...) + b = appendUint64(b, d.v1) + b = appendUint64(b, d.v2) + b = appendUint64(b, d.v3) + b = appendUint64(b, d.v4) + b = appendUint64(b, d.total) + b = append(b, d.mem[:d.n]...) + b = b[:len(b)+len(d.mem)-d.n] + return b, nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (d *Digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic) || string(b[:len(magic)]) != magic { + return errors.New("xxhash: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("xxhash: invalid hash state size") + } + b = b[len(magic):] + b, d.v1 = consumeUint64(b) + b, d.v2 = consumeUint64(b) + b, d.v3 = consumeUint64(b) + b, d.v4 = consumeUint64(b) + b, d.total = consumeUint64(b) + copy(d.mem[:], b) + d.n = int(d.total % uint64(len(d.mem))) + return nil +} + +func appendUint64(b []byte, x uint64) []byte { + var a [8]byte + binary.LittleEndian.PutUint64(a[:], x) + return append(b, a[:]...) +} + +func consumeUint64(b []byte) ([]byte, uint64) { + x := u64(b) + return b[8:], x +} + +func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) } +func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) } + +func round(acc, input uint64) uint64 { + acc += input * prime2 + acc = rol31(acc) + acc *= prime1 + return acc +} + +func mergeRound(acc, val uint64) uint64 { + val = round(0, val) + acc ^= val + acc = acc*prime1 + prime4 + return acc +} + +func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) } +func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) } +func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) } +func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) } +func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) } +func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) } +func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) } +func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) } diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_amd64.go b/vendor/github.com/cespare/xxhash/v2/xxhash_amd64.go new file mode 100644 index 0000000000..ad14b807f4 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_amd64.go @@ -0,0 +1,13 @@ +// +build !appengine +// +build gc +// +build !purego + +package xxhash + +// Sum64 computes the 64-bit xxHash digest of b. +// +//go:noescape +func Sum64(b []byte) uint64 + +//go:noescape +func writeBlocks(d *Digest, b []byte) int diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s b/vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s new file mode 100644 index 0000000000..be8db5bf79 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s @@ -0,0 +1,215 @@ +// +build !appengine +// +build gc +// +build !purego + +#include "textflag.h" + +// Register allocation: +// AX h +// SI pointer to advance through b +// DX n +// BX loop end +// R8 v1, k1 +// R9 v2 +// R10 v3 +// R11 v4 +// R12 tmp +// R13 prime1v +// R14 prime2v +// DI prime4v + +// round reads from and advances the buffer pointer in SI. +// It assumes that R13 has prime1v and R14 has prime2v. +#define round(r) \ + MOVQ (SI), R12 \ + ADDQ $8, SI \ + IMULQ R14, R12 \ + ADDQ R12, r \ + ROLQ $31, r \ + IMULQ R13, r + +// mergeRound applies a merge round on the two registers acc and val. +// It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v. +#define mergeRound(acc, val) \ + IMULQ R14, val \ + ROLQ $31, val \ + IMULQ R13, val \ + XORQ val, acc \ + IMULQ R13, acc \ + ADDQ DI, acc + +// func Sum64(b []byte) uint64 +TEXT ·Sum64(SB), NOSPLIT, $0-32 + // Load fixed primes. + MOVQ ·prime1v(SB), R13 + MOVQ ·prime2v(SB), R14 + MOVQ ·prime4v(SB), DI + + // Load slice. + MOVQ b_base+0(FP), SI + MOVQ b_len+8(FP), DX + LEAQ (SI)(DX*1), BX + + // The first loop limit will be len(b)-32. + SUBQ $32, BX + + // Check whether we have at least one block. + CMPQ DX, $32 + JLT noBlocks + + // Set up initial state (v1, v2, v3, v4). + MOVQ R13, R8 + ADDQ R14, R8 + MOVQ R14, R9 + XORQ R10, R10 + XORQ R11, R11 + SUBQ R13, R11 + + // Loop until SI > BX. +blockLoop: + round(R8) + round(R9) + round(R10) + round(R11) + + CMPQ SI, BX + JLE blockLoop + + MOVQ R8, AX + ROLQ $1, AX + MOVQ R9, R12 + ROLQ $7, R12 + ADDQ R12, AX + MOVQ R10, R12 + ROLQ $12, R12 + ADDQ R12, AX + MOVQ R11, R12 + ROLQ $18, R12 + ADDQ R12, AX + + mergeRound(AX, R8) + mergeRound(AX, R9) + mergeRound(AX, R10) + mergeRound(AX, R11) + + JMP afterBlocks + +noBlocks: + MOVQ ·prime5v(SB), AX + +afterBlocks: + ADDQ DX, AX + + // Right now BX has len(b)-32, and we want to loop until SI > len(b)-8. + ADDQ $24, BX + + CMPQ SI, BX + JG fourByte + +wordLoop: + // Calculate k1. + MOVQ (SI), R8 + ADDQ $8, SI + IMULQ R14, R8 + ROLQ $31, R8 + IMULQ R13, R8 + + XORQ R8, AX + ROLQ $27, AX + IMULQ R13, AX + ADDQ DI, AX + + CMPQ SI, BX + JLE wordLoop + +fourByte: + ADDQ $4, BX + CMPQ SI, BX + JG singles + + MOVL (SI), R8 + ADDQ $4, SI + IMULQ R13, R8 + XORQ R8, AX + + ROLQ $23, AX + IMULQ R14, AX + ADDQ ·prime3v(SB), AX + +singles: + ADDQ $4, BX + CMPQ SI, BX + JGE finalize + +singlesLoop: + MOVBQZX (SI), R12 + ADDQ $1, SI + IMULQ ·prime5v(SB), R12 + XORQ R12, AX + + ROLQ $11, AX + IMULQ R13, AX + + CMPQ SI, BX + JL singlesLoop + +finalize: + MOVQ AX, R12 + SHRQ $33, R12 + XORQ R12, AX + IMULQ R14, AX + MOVQ AX, R12 + SHRQ $29, R12 + XORQ R12, AX + IMULQ ·prime3v(SB), AX + MOVQ AX, R12 + SHRQ $32, R12 + XORQ R12, AX + + MOVQ AX, ret+24(FP) + RET + +// writeBlocks uses the same registers as above except that it uses AX to store +// the d pointer. + +// func writeBlocks(d *Digest, b []byte) int +TEXT ·writeBlocks(SB), NOSPLIT, $0-40 + // Load fixed primes needed for round. + MOVQ ·prime1v(SB), R13 + MOVQ ·prime2v(SB), R14 + + // Load slice. + MOVQ b_base+8(FP), SI + MOVQ b_len+16(FP), DX + LEAQ (SI)(DX*1), BX + SUBQ $32, BX + + // Load vN from d. + MOVQ d+0(FP), AX + MOVQ 0(AX), R8 // v1 + MOVQ 8(AX), R9 // v2 + MOVQ 16(AX), R10 // v3 + MOVQ 24(AX), R11 // v4 + + // We don't need to check the loop condition here; this function is + // always called with at least one block of data to process. +blockLoop: + round(R8) + round(R9) + round(R10) + round(R11) + + CMPQ SI, BX + JLE blockLoop + + // Copy vN back to d. + MOVQ R8, 0(AX) + MOVQ R9, 8(AX) + MOVQ R10, 16(AX) + MOVQ R11, 24(AX) + + // The number of bytes written is SI minus the old base pointer. + SUBQ b_base+8(FP), SI + MOVQ SI, ret+32(FP) + + RET diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_other.go b/vendor/github.com/cespare/xxhash/v2/xxhash_other.go new file mode 100644 index 0000000000..4a5a821603 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_other.go @@ -0,0 +1,76 @@ +// +build !amd64 appengine !gc purego + +package xxhash + +// Sum64 computes the 64-bit xxHash digest of b. +func Sum64(b []byte) uint64 { + // A simpler version would be + // d := New() + // d.Write(b) + // return d.Sum64() + // but this is faster, particularly for small inputs. + + n := len(b) + var h uint64 + + if n >= 32 { + v1 := prime1v + prime2 + v2 := prime2 + v3 := uint64(0) + v4 := -prime1v + for len(b) >= 32 { + v1 = round(v1, u64(b[0:8:len(b)])) + v2 = round(v2, u64(b[8:16:len(b)])) + v3 = round(v3, u64(b[16:24:len(b)])) + v4 = round(v4, u64(b[24:32:len(b)])) + b = b[32:len(b):len(b)] + } + h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4) + h = mergeRound(h, v1) + h = mergeRound(h, v2) + h = mergeRound(h, v3) + h = mergeRound(h, v4) + } else { + h = prime5 + } + + h += uint64(n) + + i, end := 0, len(b) + for ; i+8 <= end; i += 8 { + k1 := round(0, u64(b[i:i+8:len(b)])) + h ^= k1 + h = rol27(h)*prime1 + prime4 + } + if i+4 <= end { + h ^= uint64(u32(b[i:i+4:len(b)])) * prime1 + h = rol23(h)*prime2 + prime3 + i += 4 + } + for ; i < end; i++ { + h ^= uint64(b[i]) * prime5 + h = rol11(h) * prime1 + } + + h ^= h >> 33 + h *= prime2 + h ^= h >> 29 + h *= prime3 + h ^= h >> 32 + + return h +} + +func writeBlocks(d *Digest, b []byte) int { + v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4 + n := len(b) + for len(b) >= 32 { + v1 = round(v1, u64(b[0:8:len(b)])) + v2 = round(v2, u64(b[8:16:len(b)])) + v3 = round(v3, u64(b[16:24:len(b)])) + v4 = round(v4, u64(b[24:32:len(b)])) + b = b[32:len(b):len(b)] + } + d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4 + return n - len(b) +} diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go b/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go new file mode 100644 index 0000000000..fc9bea7a31 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_safe.go @@ -0,0 +1,15 @@ +// +build appengine + +// This file contains the safe implementations of otherwise unsafe-using code. + +package xxhash + +// Sum64String computes the 64-bit xxHash digest of s. +func Sum64String(s string) uint64 { + return Sum64([]byte(s)) +} + +// WriteString adds more data to d. It always returns len(s), nil. +func (d *Digest) WriteString(s string) (n int, err error) { + return d.Write([]byte(s)) +} diff --git a/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go b/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go new file mode 100644 index 0000000000..376e0ca2e4 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go @@ -0,0 +1,57 @@ +// +build !appengine + +// This file encapsulates usage of unsafe. +// xxhash_safe.go contains the safe implementations. + +package xxhash + +import ( + "unsafe" +) + +// In the future it's possible that compiler optimizations will make these +// XxxString functions unnecessary by realizing that calls such as +// Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205. +// If that happens, even if we keep these functions they can be replaced with +// the trivial safe code. + +// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is: +// +// var b []byte +// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) +// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data +// bh.Len = len(s) +// bh.Cap = len(s) +// +// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough +// weight to this sequence of expressions that any function that uses it will +// not be inlined. Instead, the functions below use a different unsafe +// conversion designed to minimize the inliner weight and allow both to be +// inlined. There is also a test (TestInlining) which verifies that these are +// inlined. +// +// See https://github.com/golang/go/issues/42739 for discussion. + +// Sum64String computes the 64-bit xxHash digest of s. +// It may be faster than Sum64([]byte(s)) by avoiding a copy. +func Sum64String(s string) uint64 { + b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})) + return Sum64(b) +} + +// WriteString adds more data to d. It always returns len(s), nil. +// It may be faster than Write([]byte(s)) by avoiding a copy. +func (d *Digest) WriteString(s string) (n int, err error) { + d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))) + // d.Write always returns len(s), nil. + // Ignoring the return output and returning these fixed values buys a + // savings of 6 in the inliner's cost model. + return len(s), nil +} + +// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout +// of the first two words is the same as the layout of a string. +type sliceHeader struct { + s string + cap int +} diff --git a/vendor/github.com/cespare/xxhash/xxhash.go b/vendor/github.com/cespare/xxhash/xxhash.go new file mode 100644 index 0000000000..f896bd28f0 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/xxhash.go @@ -0,0 +1,168 @@ +// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described +// at http://cyan4973.github.io/xxHash/. +package xxhash + +import ( + "encoding/binary" + "hash" +) + +const ( + prime1 uint64 = 11400714785074694791 + prime2 uint64 = 14029467366897019727 + prime3 uint64 = 1609587929392839161 + prime4 uint64 = 9650029242287828579 + prime5 uint64 = 2870177450012600261 +) + +// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where +// possible in the Go code is worth a small (but measurable) performance boost +// by avoiding some MOVQs. Vars are needed for the asm and also are useful for +// convenience in the Go code in a few places where we need to intentionally +// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the +// result overflows a uint64). +var ( + prime1v = prime1 + prime2v = prime2 + prime3v = prime3 + prime4v = prime4 + prime5v = prime5 +) + +type xxh struct { + v1 uint64 + v2 uint64 + v3 uint64 + v4 uint64 + total int + mem [32]byte + n int // how much of mem is used +} + +// New creates a new hash.Hash64 that implements the 64-bit xxHash algorithm. +func New() hash.Hash64 { + var x xxh + x.Reset() + return &x +} + +func (x *xxh) Reset() { + x.n = 0 + x.total = 0 + x.v1 = prime1v + prime2 + x.v2 = prime2 + x.v3 = 0 + x.v4 = -prime1v +} + +func (x *xxh) Size() int { return 8 } +func (x *xxh) BlockSize() int { return 32 } + +// Write adds more data to x. It always returns len(b), nil. +func (x *xxh) Write(b []byte) (n int, err error) { + n = len(b) + x.total += len(b) + + if x.n+len(b) < 32 { + // This new data doesn't even fill the current block. + copy(x.mem[x.n:], b) + x.n += len(b) + return + } + + if x.n > 0 { + // Finish off the partial block. + copy(x.mem[x.n:], b) + x.v1 = round(x.v1, u64(x.mem[0:8])) + x.v2 = round(x.v2, u64(x.mem[8:16])) + x.v3 = round(x.v3, u64(x.mem[16:24])) + x.v4 = round(x.v4, u64(x.mem[24:32])) + b = b[32-x.n:] + x.n = 0 + } + + if len(b) >= 32 { + // One or more full blocks left. + b = writeBlocks(x, b) + } + + // Store any remaining partial block. + copy(x.mem[:], b) + x.n = len(b) + + return +} + +func (x *xxh) Sum(b []byte) []byte { + s := x.Sum64() + return append( + b, + byte(s>>56), + byte(s>>48), + byte(s>>40), + byte(s>>32), + byte(s>>24), + byte(s>>16), + byte(s>>8), + byte(s), + ) +} + +func (x *xxh) Sum64() uint64 { + var h uint64 + + if x.total >= 32 { + v1, v2, v3, v4 := x.v1, x.v2, x.v3, x.v4 + h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4) + h = mergeRound(h, v1) + h = mergeRound(h, v2) + h = mergeRound(h, v3) + h = mergeRound(h, v4) + } else { + h = x.v3 + prime5 + } + + h += uint64(x.total) + + i, end := 0, x.n + for ; i+8 <= end; i += 8 { + k1 := round(0, u64(x.mem[i:i+8])) + h ^= k1 + h = rol27(h)*prime1 + prime4 + } + if i+4 <= end { + h ^= uint64(u32(x.mem[i:i+4])) * prime1 + h = rol23(h)*prime2 + prime3 + i += 4 + } + for i < end { + h ^= uint64(x.mem[i]) * prime5 + h = rol11(h) * prime1 + i++ + } + + h ^= h >> 33 + h *= prime2 + h ^= h >> 29 + h *= prime3 + h ^= h >> 32 + + return h +} + +func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) } +func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) } + +func round(acc, input uint64) uint64 { + acc += input * prime2 + acc = rol31(acc) + acc *= prime1 + return acc +} + +func mergeRound(acc, val uint64) uint64 { + val = round(0, val) + acc ^= val + acc = acc*prime1 + prime4 + return acc +} diff --git a/vendor/github.com/cespare/xxhash/xxhash_amd64.go b/vendor/github.com/cespare/xxhash/xxhash_amd64.go new file mode 100644 index 0000000000..d617652680 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/xxhash_amd64.go @@ -0,0 +1,12 @@ +// +build !appengine +// +build gc +// +build !purego + +package xxhash + +// Sum64 computes the 64-bit xxHash digest of b. +// +//go:noescape +func Sum64(b []byte) uint64 + +func writeBlocks(x *xxh, b []byte) []byte diff --git a/vendor/github.com/cespare/xxhash/xxhash_amd64.s b/vendor/github.com/cespare/xxhash/xxhash_amd64.s new file mode 100644 index 0000000000..757f2011f0 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/xxhash_amd64.s @@ -0,0 +1,233 @@ +// +build !appengine +// +build gc +// +build !purego + +#include "textflag.h" + +// Register allocation: +// AX h +// CX pointer to advance through b +// DX n +// BX loop end +// R8 v1, k1 +// R9 v2 +// R10 v3 +// R11 v4 +// R12 tmp +// R13 prime1v +// R14 prime2v +// R15 prime4v + +// round reads from and advances the buffer pointer in CX. +// It assumes that R13 has prime1v and R14 has prime2v. +#define round(r) \ + MOVQ (CX), R12 \ + ADDQ $8, CX \ + IMULQ R14, R12 \ + ADDQ R12, r \ + ROLQ $31, r \ + IMULQ R13, r + +// mergeRound applies a merge round on the two registers acc and val. +// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v. +#define mergeRound(acc, val) \ + IMULQ R14, val \ + ROLQ $31, val \ + IMULQ R13, val \ + XORQ val, acc \ + IMULQ R13, acc \ + ADDQ R15, acc + +// func Sum64(b []byte) uint64 +TEXT ·Sum64(SB), NOSPLIT, $0-32 + // Load fixed primes. + MOVQ ·prime1v(SB), R13 + MOVQ ·prime2v(SB), R14 + MOVQ ·prime4v(SB), R15 + + // Load slice. + MOVQ b_base+0(FP), CX + MOVQ b_len+8(FP), DX + LEAQ (CX)(DX*1), BX + + // The first loop limit will be len(b)-32. + SUBQ $32, BX + + // Check whether we have at least one block. + CMPQ DX, $32 + JLT noBlocks + + // Set up initial state (v1, v2, v3, v4). + MOVQ R13, R8 + ADDQ R14, R8 + MOVQ R14, R9 + XORQ R10, R10 + XORQ R11, R11 + SUBQ R13, R11 + + // Loop until CX > BX. +blockLoop: + round(R8) + round(R9) + round(R10) + round(R11) + + CMPQ CX, BX + JLE blockLoop + + MOVQ R8, AX + ROLQ $1, AX + MOVQ R9, R12 + ROLQ $7, R12 + ADDQ R12, AX + MOVQ R10, R12 + ROLQ $12, R12 + ADDQ R12, AX + MOVQ R11, R12 + ROLQ $18, R12 + ADDQ R12, AX + + mergeRound(AX, R8) + mergeRound(AX, R9) + mergeRound(AX, R10) + mergeRound(AX, R11) + + JMP afterBlocks + +noBlocks: + MOVQ ·prime5v(SB), AX + +afterBlocks: + ADDQ DX, AX + + // Right now BX has len(b)-32, and we want to loop until CX > len(b)-8. + ADDQ $24, BX + + CMPQ CX, BX + JG fourByte + +wordLoop: + // Calculate k1. + MOVQ (CX), R8 + ADDQ $8, CX + IMULQ R14, R8 + ROLQ $31, R8 + IMULQ R13, R8 + + XORQ R8, AX + ROLQ $27, AX + IMULQ R13, AX + ADDQ R15, AX + + CMPQ CX, BX + JLE wordLoop + +fourByte: + ADDQ $4, BX + CMPQ CX, BX + JG singles + + MOVL (CX), R8 + ADDQ $4, CX + IMULQ R13, R8 + XORQ R8, AX + + ROLQ $23, AX + IMULQ R14, AX + ADDQ ·prime3v(SB), AX + +singles: + ADDQ $4, BX + CMPQ CX, BX + JGE finalize + +singlesLoop: + MOVBQZX (CX), R12 + ADDQ $1, CX + IMULQ ·prime5v(SB), R12 + XORQ R12, AX + + ROLQ $11, AX + IMULQ R13, AX + + CMPQ CX, BX + JL singlesLoop + +finalize: + MOVQ AX, R12 + SHRQ $33, R12 + XORQ R12, AX + IMULQ R14, AX + MOVQ AX, R12 + SHRQ $29, R12 + XORQ R12, AX + IMULQ ·prime3v(SB), AX + MOVQ AX, R12 + SHRQ $32, R12 + XORQ R12, AX + + MOVQ AX, ret+24(FP) + RET + +// writeBlocks uses the same registers as above except that it uses AX to store +// the x pointer. + +// func writeBlocks(x *xxh, b []byte) []byte +TEXT ·writeBlocks(SB), NOSPLIT, $0-56 + // Load fixed primes needed for round. + MOVQ ·prime1v(SB), R13 + MOVQ ·prime2v(SB), R14 + + // Load slice. + MOVQ b_base+8(FP), CX + MOVQ CX, ret_base+32(FP) // initialize return base pointer; see NOTE below + MOVQ b_len+16(FP), DX + LEAQ (CX)(DX*1), BX + SUBQ $32, BX + + // Load vN from x. + MOVQ x+0(FP), AX + MOVQ 0(AX), R8 // v1 + MOVQ 8(AX), R9 // v2 + MOVQ 16(AX), R10 // v3 + MOVQ 24(AX), R11 // v4 + + // We don't need to check the loop condition here; this function is + // always called with at least one block of data to process. +blockLoop: + round(R8) + round(R9) + round(R10) + round(R11) + + CMPQ CX, BX + JLE blockLoop + + // Copy vN back to x. + MOVQ R8, 0(AX) + MOVQ R9, 8(AX) + MOVQ R10, 16(AX) + MOVQ R11, 24(AX) + + // Construct return slice. + // NOTE: It's important that we don't construct a slice that has a base + // pointer off the end of the original slice, as in Go 1.7+ this will + // cause runtime crashes. (See discussion in, for example, + // https://github.com/golang/go/issues/16772.) + // Therefore, we calculate the length/cap first, and if they're zero, we + // keep the old base. This is what the compiler does as well if you + // write code like + // b = b[len(b):] + + // New length is 32 - (CX - BX) -> BX+32 - CX. + ADDQ $32, BX + SUBQ CX, BX + JZ afterSetBase + + MOVQ CX, ret_base+32(FP) + +afterSetBase: + MOVQ BX, ret_len+40(FP) + MOVQ BX, ret_cap+48(FP) // set cap == len + + RET diff --git a/vendor/github.com/cespare/xxhash/xxhash_other.go b/vendor/github.com/cespare/xxhash/xxhash_other.go new file mode 100644 index 0000000000..c68d13f89e --- /dev/null +++ b/vendor/github.com/cespare/xxhash/xxhash_other.go @@ -0,0 +1,75 @@ +// +build !amd64 appengine !gc purego + +package xxhash + +// Sum64 computes the 64-bit xxHash digest of b. +func Sum64(b []byte) uint64 { + // A simpler version would be + // x := New() + // x.Write(b) + // return x.Sum64() + // but this is faster, particularly for small inputs. + + n := len(b) + var h uint64 + + if n >= 32 { + v1 := prime1v + prime2 + v2 := prime2 + v3 := uint64(0) + v4 := -prime1v + for len(b) >= 32 { + v1 = round(v1, u64(b[0:8:len(b)])) + v2 = round(v2, u64(b[8:16:len(b)])) + v3 = round(v3, u64(b[16:24:len(b)])) + v4 = round(v4, u64(b[24:32:len(b)])) + b = b[32:len(b):len(b)] + } + h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4) + h = mergeRound(h, v1) + h = mergeRound(h, v2) + h = mergeRound(h, v3) + h = mergeRound(h, v4) + } else { + h = prime5 + } + + h += uint64(n) + + i, end := 0, len(b) + for ; i+8 <= end; i += 8 { + k1 := round(0, u64(b[i:i+8:len(b)])) + h ^= k1 + h = rol27(h)*prime1 + prime4 + } + if i+4 <= end { + h ^= uint64(u32(b[i:i+4:len(b)])) * prime1 + h = rol23(h)*prime2 + prime3 + i += 4 + } + for ; i < end; i++ { + h ^= uint64(b[i]) * prime5 + h = rol11(h) * prime1 + } + + h ^= h >> 33 + h *= prime2 + h ^= h >> 29 + h *= prime3 + h ^= h >> 32 + + return h +} + +func writeBlocks(x *xxh, b []byte) []byte { + v1, v2, v3, v4 := x.v1, x.v2, x.v3, x.v4 + for len(b) >= 32 { + v1 = round(v1, u64(b[0:8:len(b)])) + v2 = round(v2, u64(b[8:16:len(b)])) + v3 = round(v3, u64(b[16:24:len(b)])) + v4 = round(v4, u64(b[24:32:len(b)])) + b = b[32:len(b):len(b)] + } + x.v1, x.v2, x.v3, x.v4 = v1, v2, v3, v4 + return b +} diff --git a/vendor/github.com/cespare/xxhash/xxhash_safe.go b/vendor/github.com/cespare/xxhash/xxhash_safe.go new file mode 100644 index 0000000000..dfa15ab7e2 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/xxhash_safe.go @@ -0,0 +1,10 @@ +// +build appengine + +// This file contains the safe implementations of otherwise unsafe-using code. + +package xxhash + +// Sum64String computes the 64-bit xxHash digest of s. +func Sum64String(s string) uint64 { + return Sum64([]byte(s)) +} diff --git a/vendor/github.com/cespare/xxhash/xxhash_unsafe.go b/vendor/github.com/cespare/xxhash/xxhash_unsafe.go new file mode 100644 index 0000000000..d2b64e8bb0 --- /dev/null +++ b/vendor/github.com/cespare/xxhash/xxhash_unsafe.go @@ -0,0 +1,30 @@ +// +build !appengine + +// This file encapsulates usage of unsafe. +// xxhash_safe.go contains the safe implementations. + +package xxhash + +import ( + "reflect" + "unsafe" +) + +// Sum64String computes the 64-bit xxHash digest of s. +// It may be faster than Sum64([]byte(s)) by avoiding a copy. +// +// TODO(caleb): Consider removing this if an optimization is ever added to make +// it unnecessary: https://golang.org/issue/2205. +// +// TODO(caleb): We still have a function call; we could instead write Go/asm +// copies of Sum64 for strings to squeeze out a bit more speed. +func Sum64String(s string) uint64 { + // See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ + // for some discussion about this unsafe conversion. + var b []byte + bh := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data + bh.Len = len(s) + bh.Cap = len(s) + return Sum64(b) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/.deepsource.toml b/vendor/github.com/dgraph-io/badger/v3/.deepsource.toml new file mode 100644 index 0000000000..266045f0ec --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/.deepsource.toml @@ -0,0 +1,18 @@ +version = 1 + +test_patterns = [ + 'integration/testgc/**', + '**/*_test.go' +] + +exclude_patterns = [ + +] + +[[analyzers]] +name = 'go' +enabled = true + + + [analyzers.meta] + import_path = 'github.com/dgraph-io/badger' diff --git a/vendor/github.com/dgraph-io/badger/v3/.gitignore b/vendor/github.com/dgraph-io/badger/v3/.gitignore new file mode 100644 index 0000000000..f78c74c4c9 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/.gitignore @@ -0,0 +1,5 @@ +p/ +badger-test*/ +.idea/ + +vendor \ No newline at end of file diff --git a/vendor/github.com/dgraph-io/badger/v3/.golangci.yml b/vendor/github.com/dgraph-io/badger/v3/.golangci.yml new file mode 100644 index 0000000000..fecb8644b8 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/.golangci.yml @@ -0,0 +1,27 @@ +run: + tests: false + +linters-settings: + lll: + line-length: 100 + +linters: + disable-all: true + enable: + - errcheck + - ineffassign + - gas + - gofmt + - golint + - gosimple + - govet + - lll + - varcheck + - unused + +issues: + exclude-rules: + - linters: + - gosec + text: "G404: " + \ No newline at end of file diff --git a/vendor/github.com/dgraph-io/badger/v3/.travis.yml b/vendor/github.com/dgraph-io/badger/v3/.travis.yml new file mode 100644 index 0000000000..b671868f3c --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/.travis.yml @@ -0,0 +1,48 @@ +language: go + +go: + - "1.12" + - "1.13" + - tip +os: + - osx +env: + jobs: + - GOARCH=386 + - GOARCH=amd64 + global: + - secure: CRkV2+/jlO0gXzzS50XGxfMS117FNwiVjxNY/LeWq06RKD+dDCPxTJl3JCNe3l0cYEPAglV2uMMYukDiTqJ7e+HI4nh4N4mv6lwx39N8dAvJe1x5ITS2T4qk4kTjuQb1Q1vw/ZOxoQqmvNKj2uRmBdJ/HHmysbRJ1OzCWML3OXdUwJf0AYlJzTjpMfkOKr7sTtE4rwyyQtd4tKH1fGdurgI9ZuFd9qvYxK2qcJhsQ6CNqMXt+7FkVkN1rIPmofjjBTNryzUr4COFXuWH95aDAif19DeBW4lbNgo1+FpDsrgmqtuhl6NAuptI8q/imow2KXBYJ8JPXsxW8DVFj0IIp0RCd3GjaEnwBEbxAyiIHLfW7AudyTS/dJOvZffPqXnuJ8xj3OPIdNe4xY0hWl8Ju2HhKfLOAHq7VadHZWd3IHLil70EiL4/JLD1rNbMImUZisFaA8pyrcIvYYebjOnk4TscwKFLedClRSX1XsMjWWd0oykQtrdkHM2IxknnBpaLu7mFnfE07f6dkG0nlpyu4SCLey7hr5FdcEmljA0nIxTSYDg6035fQkBEAbe7hlESOekkVNT9IZPwG+lmt3vU4ofi6NqNbJecOuSB+h36IiZ9s4YQtxYNnLgW14zjuFGGyT5smc3IjBT7qngDjKIgyrSVoRkY/8udy9qbUgvBeW8= + + +jobs: + allow_failures: + - go: tip + exclude: + # Exclude builds for 386 architecture on go 1.12 and tip + # Since we don't want it to run for 32 bit + - go: "1.12" + env: GOARCH=386 + - go: tip + env: GOARCH=386 + include: + # Define one extra linux build, which we use to run cross + # compiled 32 bit tests + - os: linux + arch: arm64 + go: "1.14" + env: go_32=yes + +notifications: + email: false + slack: + secure: X7uBLWYbuUhf8QFE16CoS5z7WvFR8EN9j6cEectMW6mKZ3vwXGwVXRIPsgUq/606DsQdCCx34MR8MRWYGlu6TBolbSe9y0EP0i46yipPz22YtuT7umcVUbGEyx8MZKgG0v1u/zA0O4aCsOBpGAA3gxz8h3JlEHDt+hv6U8xRsSllVLzLSNb5lwxDtcfEDxVVqP47GMEgjLPM28Pyt5qwjk7o5a4YSVzkfdxBXxd3gWzFUWzJ5E3cTacli50dK4GVfiLcQY2aQYoYO7AAvDnvP+TPfjDkBlUEE4MUz5CDIN51Xb+WW33sX7g+r3Bj7V5IRcF973RiYkpEh+3eoiPnyWyxhDZBYilty3b+Hysp6d4Ov/3I3ll7Bcny5+cYjakjkMH3l9w3gs6Y82GlpSLSJshKWS8vPRsxFe0Pstj6QSJXTd9EBaFr+l1ScXjJv/Sya9j8N9FfTuOTESWuaL1auX4Y7zEEVHlA8SCNOO8K0eTfxGZnC/YcIHsR8rePEAcFxfOYQppkyLF/XvAtnb/LMUuu0g4y2qNdme6Oelvyar1tFEMRtbl4mRCdu/krXBFtkrsfUaVY6WTPdvXAGotsFJ0wuA53zGVhlcd3+xAlSlR3c1QX95HIMeivJKb5L4nTjP+xnrmQNtnVk+tG4LSH2ltuwcZSSczModtcBmRefrk= + +script: >- + if [ $TRAVIS_OS_NAME = "linux" ] && [ $go_32 ]; then + uname -a + GOOS=linux GOARCH=arm go test -v ./... + else + go test -v ./... + # Cross-compile for Plan 9 + GOOS=plan9 go build ./... + fi diff --git a/vendor/github.com/dgraph-io/badger/v3/CHANGELOG.md b/vendor/github.com/dgraph-io/badger/v3/CHANGELOG.md new file mode 100644 index 0000000000..aae5f0f9b5 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/CHANGELOG.md @@ -0,0 +1,645 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). + +## [3.2103.2] - 2021-10-07 + +### Fixed + + - fix(compact): close vlog after the compaction at L0 has completed (#1752) + - fix(builder): put the upper limit on reallocation (#1748) + - deps: Bump github.com/google/flatbuffers to v1.12.1 (#1746) + - fix(levels): Avoid a deadlock when acquiring read locks in levels (#1744) + - fix(pubsub): avoid deadlock in publisher and subscriber (#1749) (#1751) + +## [3.2103.1] - 2021-07-08 + +### Fixed + - fix(compaction): copy over the file ID when building tables #1713 + - fix: Fix conflict detection for managed DB (#1716) + - fix(pendingWrites): don't skip the pending entries with version=0 (#1721) + +### Features + - feat(zstd): replace datadog's zstd with Klauspost's zstd (#1709) + +## [3.2103.0] - 2021-06-02 + +### Breaking + - Subscribe: Add option to subscribe with holes in prefixes. (#1658) + +### Fixed + - fix(compaction): Remove compaction backoff mechanism (#1686) + - Add a name to mutexes to make them unexported (#1678) + - fix(merge-operator): don't read the deleted keys (#1675) + - fix(discard): close the discard stats file on db close (#1672) + - fix(iterator): fix iterator when data does not exist in read only mode (#1670) + - fix(badger): Do not reuse variable across badger commands (#1624) + - fix(dropPrefix): check properly if the key is present in a table (#1623) + +### Performance + - Opt(Stream): Optimize how we deduce key ranges for iteration (#1687) + - Increase value threshold from 1 KB to 1 MB (#1664) + - opt(DropPrefix): check if there exist some data to drop before dropping prefixes (#1621) + +### Features + - feat(options): allow special handling and checking when creating options from superflag (#1688) + - overwrite default Options from SuperFlag string (#1663) + - Support SinceTs in iterators (#1653) + - feat(info): Add a flag to parse and print DISCARD file (#1662) + - feat(vlog): making vlog threshold dynamic 6ce3b7c (#1635) + - feat(options): add NumGoroutines option for default Stream.numGo (#1656) + - feat(Trie): Working prefix match with holes (#1654) + - feat: add functionality to ban a prefix (#1638) + - feat(compaction): Support Lmax to Lmax compaction (#1615) + +### New APIs +- Badger.DB + - BanNamespace + - BannedNamespaces + - Ranges +- Badger.Options + - FromSuperFlag + - WithNumGoRoutines + - WithNamespaceOffset + - WithVLogPercentile +- Badger.Trie + - AddMatch + - DeleteMatch +- Badger.Table + - StaleDataSize +- Badger.Table.Builder + - AddStaleKey +- Badger.InitDiscardStats + +### Removed APIs +- Badger.DB + - KeySplits +- Badger.Options + - SkipVlog + +### Changed APIs +- Badger.DB + - Subscribe +- Badger.Options + - WithValueThreshold + +## [3.2011.1] - 2021-01-22 + +### Fixed + - Fix(compaction): Set base level correctly after stream (#1651) + - Fix: update ristretto and use filepath (#1652) + - Fix(badger): Do not reuse variable across badger commands (#1650) + - Fix(build): fix 32-bit build (#1646) + - Fix(table): always sync SST to disk (#1645) + +## [3.2011.0] - 2021-01-15 + +This release is not backward compatible with Badger v2.x.x + +### Breaking: + - opt(compactions): Improve compaction performance (#1574) + - Change how Badger handles WAL (#1555) + - feat(index): Use flatbuffers instead of protobuf (#1546) + +### Fixed: + - Fix(GC): Set bits correctly for moved keys (#1619) + - Fix(tableBuilding): reduce scope of valuePointer (#1617) + - Fix(compaction): fix table size estimation on compaction (#1613) + - Fix(OOM): Reuse pb.KVs in Stream (#1609) + - Fix race condition in L0StallMs variable (#1605) + - Fix(stream): Stop produceKVs on error (#1604) + - Fix(skiplist): Remove z.Buffer from skiplist (#1600) + - Fix(readonly): fix the file opening mode (#1592) + - Fix: Disable CompactL0OnClose by default (#1586) + - Fix(compaction): Don't drop data when split overlaps with top tables (#1587) + - Fix(subcompaction): Close builder before throttle.Done (#1582) + - Fix(table): Add onDisk size (#1569) + - Fix(Stream): Only send done markers if told to do so + - Fix(value log GC): Fix a bug which caused value log files to not be GCed. + - Fix segmentation fault when cache sizes are small. (#1552) + - Fix(builder): Too many small tables when compression is enabled (#1549) + - Fix integer overflow error when building for 386 (#1541) + - Fix(writeBatch): Avoid deadlock in commit callback (#1529) + - Fix(db): Handle nil logger (#1534) + - Fix(maxVersion): Use choosekey instead of KeyToList (#1532) + - Fix(Backup/Restore): Keep all versions (#1462) + - Fix(build): Fix nocgo builds. (#1493) + - Fix(cleanup): Avoid truncating in value.Open on error (#1465) + - Fix(compaction): Don't use cache for table compaction (#1467) + - Fix(compaction): Use separate compactors for L0, L1 (#1466) + - Fix(options): Do not implicitly enable cache (#1458) + - Fix(cleanup): Do not close cache before compaction (#1464) + - Fix(replay): Update head for LSM entires also (#1456) + - fix(levels): Cleanup builder resources on building an empty table (#1414) + +### Performance + - perf(GC): Remove move keys (#1539) + - Keep the cheaper parts of the index within table struct. (#1608) + - Opt(stream): Use z.Buffer to stream data (#1606) + - opt(builder): Use z.Allocator for building tables (#1576) + - opt(memory): Use z.Calloc for allocating KVList (#1563) + - opt: Small memory usage optimizations (#1562) + - KeySplits checks tables and memtables when number of splits is small. (#1544) + - perf: Reduce memory usage by better struct packing (#1528) + - perf(tableIterator): Don't do next on NewIterator (#1512) + - Improvements: Manual Memory allocation via Calloc (#1459) + - Various bug fixes: Break up list and run DropAll func (#1439) + - Add a limit to the size of the batches sent over a stream. (#1412) + - Commit does not panic after Finish, instead returns an error (#1396) + - levels: Compaction incorrectly drops some delete markers (#1422) + - Remove vlog file if bootstrap, syncDir or mmap fails (#1434) + +### Features: + - Use opencensus for tracing (#1566) + - Export functions from Key Registry (#1561) + - Allow sizes of block and index caches to be updated. (#1551) + - Add metric for number of tables being compacted (#1554) + - feat(info): Show index and bloom filter size (#1543) + - feat(db): Add db.MaxVersion API (#1526) + - Expose DB options in Badger. (#1521) + - Feature: Add a Calloc based Buffer (#1471) + - Add command to stream contents of DB into another DB. (#1463) + - Expose NumAlloc metrics via expvar (#1470) + - Support fully disabling the bloom filter (#1319) + - Add --enc-key flag in badger info tool (#1441) + +### New APIs +- Badger.DB + - CacheMaxCost (#1551) + - Levels (#1574) + - LevelsToString (#1574) + - Opts (#1521) +- Badger.Options + - WithBaseLevelSize (#1574) + - WithBaseTableSize (#1574) + - WithMemTableSize (#1574) +- Badger.KeyRegistry + - DataKey (#1561) + - LatestDataKey (#1561) + +### Removed APIs +- Badger.Options + - WithKeepL0InMemory (#1555) + - WithLevelOneSize (#1574) + - WithLoadBloomsOnOpen (#1555) + - WithLogRotatesToFlush (#1574) + - WithMaxTableSize (#1574) + - WithTableLoadingMode (#1555) + - WithTruncate (#1555) + - WithValueLogLoadingMode (#1555) + +## [2.2007.2] - 2020-08-31 + +### Fixed + - Compaction: Use separate compactors for L0, L1 (#1466) + - Rework Block and Index cache (#1473) + - Add IsClosed method (#1478) + - Cleanup: Avoid truncating in vlog.Open on error (#1465) + - Cleanup: Do not close cache before compactions (#1464) + +### New APIs +- Badger.DB + - BlockCacheMetrics (#1473) + - IndexCacheMetrics (#1473) +- Badger.Option + - WithBlockCacheSize (#1473) + - WithIndexCacheSize (#1473) + +### Removed APIs [Breaking Changes] +- Badger.DB + - DataCacheMetrics (#1473) + - BfCacheMetrics (#1473) +- Badger.Option + - WithMaxCacheSize (#1473) + - WithMaxBfCacheSize (#1473) + - WithKeepBlockIndicesInCache (#1473) + - WithKeepBlocksInCache (#1473) + +## [2.2007.1] - 2020-08-19 + +### Fixed + - Remove vlog file if bootstrap, syncDir or mmap fails (#1434) + - levels: Compaction incorrectly drops some delete markers (#1422) + - Replay: Update head for LSM entires also (#1456) + +## [2.2007.0] - 2020-08-10 + +### Fixed + - Add a limit to the size of the batches sent over a stream. (#1412) + - Fix Sequence generates duplicate values (#1281) + - Fix race condition in DoesNotHave (#1287) + - Fail fast if cgo is disabled and compression is ZSTD (#1284) + - Proto: make badger/v2 compatible with v1 (#1293) + - Proto: Rename dgraph.badger.v2.pb to badgerpb2 (#1314) + - Handle duplicates in ManagedWriteBatch (#1315) + - Ensure `bitValuePointer` flag is cleared for LSM entry values written to LSM (#1313) + - DropPrefix: Return error on blocked writes (#1329) + - Confirm `badgerMove` entry required before rewrite (#1302) + - Drop move keys when its key prefix is dropped (#1331) + - Iterator: Always add key to txn.reads (#1328) + - Restore: Account for value size as well (#1358) + - Compaction: Expired keys and delete markers are never purged (#1354) + - GC: Consider size of value while rewriting (#1357) + - Force KeepL0InMemory to be true when InMemory is true (#1375) + - Rework DB.DropPrefix (#1381) + - Update head while replaying value log (#1372) + - Avoid panic on multiple closer.Signal calls (#1401) + - Return error if the vlog writes exceeds more than 4GB (#1400) + +### Performance + - Clean up transaction oracle as we go (#1275) + - Use cache for storing block offsets (#1336) + +### Features + - Support disabling conflict detection (#1344) + - Add leveled logging (#1249) + - Support entry version in Write batch (#1310) + - Add Write method to batch write (#1321) + - Support multiple iterators in read-write transactions (#1286) + +### New APIs +- Badger.DB + - NewManagedWriteBatch (#1310) + - DropPrefix (#1381) +- Badger.Option + - WithDetectConflicts (#1344) + - WithKeepBlockIndicesInCache (#1336) + - WithKeepBlocksInCache (#1336) +- Badger.WriteBatch + - DeleteAt (#1310) + - SetEntryAt (#1310) + - Write (#1321) + +### Changes to Default Options + - DefaultOptions: Set KeepL0InMemory to false (#1345) + - Increase default valueThreshold from 32B to 1KB (#1346) + +### Deprecated +- Badger.Option + - WithEventLogging (#1203) + +### Reverts +This sections lists the changes which were reverted because of non-reproducible crashes. +- Compress/Encrypt Blocks in the background (#1227) + + +## [2.0.3] - 2020-03-24 + +### Fixed + +- Add support for watching nil prefix in subscribe API (#1246) + +### Performance + +- Compress/Encrypt Blocks in the background (#1227) +- Disable cache by default (#1257) + +### Features + +- Add BypassDirLock option (#1243) +- Add separate cache for bloomfilters (#1260) + +### New APIs +- badger.DB + - BfCacheMetrics (#1260) + - DataCacheMetrics (#1260) +- badger.Options + - WithBypassLockGuard (#1243) + - WithLoadBloomsOnOpen (#1260) + - WithMaxBfCacheSize (#1260) + +## [2.0.3] - 2020-03-24 + +### Fixed + +- Add support for watching nil prefix in subscribe API (#1246) + +### Performance + +- Compress/Encrypt Blocks in the background (#1227) +- Disable cache by default (#1257) + +### Features + +- Add BypassDirLock option (#1243) +- Add separate cache for bloomfilters (#1260) + +### New APIs +- badger.DB + - BfCacheMetrics (#1260) + - DataCacheMetrics (#1260) +- badger.Options + - WithBypassLockGuard (#1243) + - WithLoadBloomsOnOpen (#1260) + - WithMaxBfCacheSize (#1260) + +## [2.0.2] - 2020-03-02 + +### Fixed + +- Cast sz to uint32 to fix compilation on 32 bit. (#1175) +- Fix checkOverlap in compaction. (#1166) +- Avoid sync in inmemory mode. (#1190) +- Support disabling the cache completely. (#1185) +- Add support for caching bloomfilters. (#1204) +- Fix int overflow for 32bit. (#1216) +- Remove the 'this entry should've caught' log from value.go. (#1170) +- Rework concurrency semantics of valueLog.maxFid. (#1187) + +### Performance + +- Use fastRand instead of locked-rand in skiplist. (#1173) +- Improve write stalling on level 0 and 1. (#1186) +- Disable compression and set ZSTD Compression Level to 1. (#1191) + +## [2.0.1] - 2020-01-02 + +### New APIs + +- badger.Options + - WithInMemory (f5b6321) + - WithZSTDCompressionLevel (3eb4e72) + +- Badger.TableInfo + - EstimatedSz (f46f8ea) + +### Features + +- Introduce in-memory mode in badger. (#1113) + +### Fixed + +- Limit manifest's change set size. (#1119) +- Cast idx to uint32 to fix compilation on i386. (#1118) +- Fix request increment ref bug. (#1121) +- Fix windows dataloss issue. (#1134) +- Fix VerifyValueChecksum checks. (#1138) +- Fix encryption in stream writer. (#1146) +- Fix segmentation fault in vlog.Read. (header.Decode) (#1150) +- Fix merge iterator duplicates issue. (#1157) + +### Performance + +- Set level 15 as default compression level in Zstd. (#1111) +- Optimize createTable in stream_writer.go. (#1132) + +## [2.0.0] - 2019-11-12 + +### New APIs + +- badger.DB + - NewWriteBatchAt (7f43769) + - CacheMetrics (b9056f1) + +- badger.Options + - WithMaxCacheSize (b9056f1) + - WithEventLogging (75c6a44) + - WithBlockSize (1439463) + - WithBloomFalsePositive (1439463) + - WithKeepL0InMemory (ee70ff2) + - WithVerifyValueChecksum (ee70ff2) + - WithCompression (5f3b061) + - WithEncryptionKey (a425b0e) + - WithEncryptionKeyRotationDuration (a425b0e) + - WithChecksumVerificationMode (7b4083d) + +### Features + +- Data cache to speed up lookups and iterations. (#1066) +- Data compression. (#1013) +- Data encryption-at-rest. (#1042) + +### Fixed + +- Fix deadlock when flushing discard stats. (#976) +- Set move key's expiresAt for keys with TTL. (#1006) +- Fix unsafe usage in Decode. (#1097) +- Fix race condition on db.orc.nextTxnTs. (#1101) +- Fix level 0 GC dataloss bug. (#1090) +- Fix deadlock in discard stats. (#1070) +- Support checksum verification for values read from vlog. (#1052) +- Store entire L0 in memory. (#963) +- Fix table.Smallest/Biggest and iterator Prefix bug. (#997) +- Use standard proto functions for Marshal/Unmarshal and Size. (#994) +- Fix boundaries on GC batch size. (#987) +- VlogSize to store correct directory name to expvar.Map. (#956) +- Fix transaction too big issue in restore. (#957) +- Fix race condition in updateDiscardStats. (#973) +- Cast results of len to uint32 to fix compilation in i386 arch. (#961) +- Making the stream writer APIs goroutine-safe. (#959) +- Fix prefix bug in key iterator and allow all versions. (#950) +- Drop discard stats if we can't unmarshal it. (#936) +- Fix race condition in flushDiscardStats function. (#921) +- Ensure rewrite in vlog is within transactional limits. (#911) +- Fix discard stats moved by GC bug. (#929) +- Fix busy-wait loop in Watermark. (#920) + +### Performance + +- Introduce fast merge iterator. (#1080) +- Binary search based table picker. (#983) +- Flush vlog buffer if it grows beyond threshold. (#1067) +- Introduce StreamDone in Stream Writer. (#1061) +- Performance Improvements to block iterator. (#977) +- Prevent unnecessary safecopy in iterator parseKV. (#971) +- Use pointers instead of binary encoding. (#965) +- Reuse block iterator inside table iterator. (#972) +- [breaking/format] Remove vlen from entry header. (#945) +- Replace FarmHash with AESHash for Oracle conflicts. (#952) +- [breaking/format] Optimize Bloom filters. (#940) +- [breaking/format] Use varint for header encoding (without header length). (#935) +- Change file picking strategy in compaction. (#894) +- [breaking/format] Block level changes. (#880) +- [breaking/format] Add key-offset index to the end of SST table. (#881) + + +## [1.6.0] - 2019-07-01 + +This is a release including almost 200 commits, so expect many changes - some of them +not backward compatible. + +Regarding backward compatibility in Badger versions, you might be interested on reading +[VERSIONING.md](VERSIONING.md). + +_Note_: The hashes in parentheses correspond to the commits that impacted the given feature. + +### New APIs + +- badger.DB + - DropPrefix (291295e) + - Flatten (7e41bba) + - KeySplits (4751ef1) + - MaxBatchCount (b65e2a3) + - MaxBatchSize (b65e2a3) + - PrintKeyValueHistogram (fd59907) + - Subscribe (26128a7) + - Sync (851e462) + +- badger.DefaultOptions() and badger.LSMOnlyOptions() (91ce687) + - badger.Options.WithX methods + +- badger.Entry (e9447c9) + - NewEntry + - WithMeta + - WithDiscard + - WithTTL + +- badger.Item + - KeySize (fd59907) + - ValueSize (5242a99) + +- badger.IteratorOptions + - PickTable (7d46029, 49a49e3) + - Prefix (7d46029) + +- badger.Logger (fbb2778) + +- badger.Options + - CompactL0OnClose (7e41bba) + - Logger (3f66663) + - LogRotatesToFlush (2237832) + +- badger.Stream (14cbd89, 3258067) +- badger.StreamWriter (7116e16) +- badger.TableInfo.KeyCount (fd59907) +- badger.TableManifest (2017987) +- badger.Tx.NewKeyIterator (49a49e3) +- badger.WriteBatch (6daccf9, 7e78e80) + +### Modified APIs + +#### Breaking changes: + +- badger.DefaultOptions and badger.LSMOnlyOptions are now functions rather than variables (91ce687) +- badger.Item.Value now receives a function that returns an error (439fd46) +- badger.Txn.Commit doesn't receive any params now (6daccf9) +- badger.DB.Tables now receives a boolean (76b5341) + +#### Not breaking changes: + +- badger.LSMOptions changed values (799c33f) +- badger.DB.NewIterator now allows multiple iterators per RO txn (41d9656) +- badger.Options.TableLoadingMode's new default is options.MemoryMap (6b97bac) + +### Removed APIs + +- badger.ManagedDB (d22c0e8) +- badger.Options.DoNotCompact (7e41bba) +- badger.Txn.SetWithX (e9447c9) + +### Tools: + +- badger bank disect (13db058) +- badger bank test (13db058) --mmap (03870e3) +- badger fill (7e41bba) +- badger flatten (7e41bba) +- badger info --histogram (fd59907) --history --lookup --show-keys --show-meta --with-prefix (09e9b63) --show-internal (fb2eed9) +- badger benchmark read (239041e) +- badger benchmark write (6d3b67d) + +## [1.5.5] - 2019-06-20 + +* Introduce support for Go Modules + +## [1.5.3] - 2018-07-11 +Bug Fixes: +* Fix a panic caused due to item.vptr not copying over vs.Value, when looking + for a move key. + +## [1.5.2] - 2018-06-19 +Bug Fixes: +* Fix the way move key gets generated. +* If a transaction has unclosed, or multiple iterators running simultaneously, + throw a panic. Every iterator must be properly closed. At any point in time, + only one iterator per transaction can be running. This is to avoid bugs in a + transaction data structure which is thread unsafe. + +* *Warning: This change might cause panics in user code. Fix is to properly + close your iterators, and only have one running at a time per transaction.* + +## [1.5.1] - 2018-06-04 +Bug Fixes: +* Fix for infinite yieldItemValue recursion. #503 +* Fix recursive addition of `badgerMove` prefix. https://github.com/dgraph-io/badger/commit/2e3a32f0ccac3066fb4206b28deb39c210c5266f +* Use file size based window size for sampling, instead of fixing it to 10MB. #501 + +Cleanup: +* Clarify comments and documentation. +* Move badger tool one directory level up. + +## [1.5.0] - 2018-05-08 +* Introduce `NumVersionsToKeep` option. This option is used to discard many + versions of the same key, which saves space. +* Add a new `SetWithDiscard` method, which would indicate that all the older + versions of the key are now invalid. Those versions would be discarded during + compactions. +* Value log GC moves are now bound to another keyspace to ensure latest versions + of data are always at the top in LSM tree. +* Introduce `ValueLogMaxEntries` to restrict the number of key-value pairs per + value log file. This helps bound the time it takes to garbage collect one + file. + +## [1.4.0] - 2018-05-04 +* Make mmap-ing of value log optional. +* Run GC multiple times, based on recorded discard statistics. +* Add MergeOperator. +* Force compact L0 on clsoe (#439). +* Add truncate option to warn about data loss (#452). +* Discard key versions during compaction (#464). +* Introduce new `LSMOnlyOptions`, to make Badger act like a typical LSM based DB. + +Bug fix: +* (Temporary) Check max version across all tables in Get (removed in next + release). +* Update commit and read ts while loading from backup. +* Ensure all transaction entries are part of the same value log file. +* On commit, run unlock callbacks before doing writes (#413). +* Wait for goroutines to finish before closing iterators (#421). + +## [1.3.0] - 2017-12-12 +* Add `DB.NextSequence()` method to generate monotonically increasing integer + sequences. +* Add `DB.Size()` method to return the size of LSM and value log files. +* Tweaked mmap code to make Windows 32-bit builds work. +* Tweaked build tags on some files to make iOS builds work. +* Fix `DB.PurgeOlderVersions()` to not violate some constraints. + +## [1.2.0] - 2017-11-30 +* Expose a `Txn.SetEntry()` method to allow setting the key-value pair + and all the metadata at the same time. + +## [1.1.1] - 2017-11-28 +* Fix bug where txn.Get was returing key deleted in same transaction. +* Fix race condition while decrementing reference in oracle. +* Update doneCommit in the callback for CommitAsync. +* Iterator see writes of current txn. + +## [1.1.0] - 2017-11-13 +* Create Badger directory if it does not exist when `badger.Open` is called. +* Added `Item.ValueCopy()` to avoid deadlocks in long-running iterations +* Fixed 64-bit alignment issues to make Badger run on Arm v7 + +## [1.0.1] - 2017-11-06 +* Fix an uint16 overflow when resizing key slice + +[Unreleased]: https://github.com/dgraph-io/badger/compare/v2.2007.2...HEAD +[2.2007.2]: https://github.com/dgraph-io/badger/compare/v2.2007.1...v2.2007.2 +[2.2007.1]: https://github.com/dgraph-io/badger/compare/v2.2007.0...v2.2007.1 +[2.2007.0]: https://github.com/dgraph-io/badger/compare/v2.0.3...v2.2007.0 +[2.0.3]: https://github.com/dgraph-io/badger/compare/v2.0.2...v2.0.3 +[2.0.2]: https://github.com/dgraph-io/badger/compare/v2.0.1...v2.0.2 +[2.0.1]: https://github.com/dgraph-io/badger/compare/v2.0.0...v2.0.1 +[2.0.0]: https://github.com/dgraph-io/badger/compare/v1.6.0...v2.0.0 +[1.6.0]: https://github.com/dgraph-io/badger/compare/v1.5.5...v1.6.0 +[1.5.5]: https://github.com/dgraph-io/badger/compare/v1.5.3...v1.5.5 +[1.5.3]: https://github.com/dgraph-io/badger/compare/v1.5.2...v1.5.3 +[1.5.2]: https://github.com/dgraph-io/badger/compare/v1.5.1...v1.5.2 +[1.5.1]: https://github.com/dgraph-io/badger/compare/v1.5.0...v1.5.1 +[1.5.0]: https://github.com/dgraph-io/badger/compare/v1.4.0...v1.5.0 +[1.4.0]: https://github.com/dgraph-io/badger/compare/v1.3.0...v1.4.0 +[1.3.0]: https://github.com/dgraph-io/badger/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/dgraph-io/badger/compare/v1.1.1...v1.2.0 +[1.1.1]: https://github.com/dgraph-io/badger/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/dgraph-io/badger/compare/v1.0.1...v1.1.0 +[1.0.1]: https://github.com/dgraph-io/badger/compare/v1.0.0...v1.0.1 diff --git a/vendor/github.com/dgraph-io/badger/v3/CODE_OF_CONDUCT.md b/vendor/github.com/dgraph-io/badger/v3/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..bf7bbc29dc --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/CODE_OF_CONDUCT.md @@ -0,0 +1,5 @@ +# Code of Conduct + +Our Code of Conduct can be found here: + +https://dgraph.io/conduct diff --git a/vendor/github.com/dgraph-io/badger/v3/CONTRIBUTING.md b/vendor/github.com/dgraph-io/badger/v3/CONTRIBUTING.md new file mode 100644 index 0000000000..30512e9dba --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/CONTRIBUTING.md @@ -0,0 +1,107 @@ +# Contribution Guide + +* [Before you get started](#before-you-get-started) + * [Code of Conduct](#code-of-conduct) +* [Your First Contribution](#your-first-contribution) + * [Find a good first topic](#find-a-good-first-topic) +* [Setting up your development environment](#setting-up-your-development-environment) + * [Fork the project](#fork-the-project) + * [Clone the project](#clone-the-project) + * [New branch for a new code](#new-branch-for-a-new-code) + * [Test](#test) + * [Commit and push](#commit-and-push) + * [Create a Pull Request](#create-a-pull-request) + * [Sign the CLA](#sign-the-cla) + * [Get a code review](#get-a-code-review) + +## Before you get started + +### Code of Conduct + +Please make sure to read and observe our [Code of Conduct](./CODE_OF_CONDUCT.md). + +## Your First Contribution + +### Find a good first topic + +You can start by finding an existing issue with the +[good first issue](https://github.com/dgraph-io/badger/labels/good%20first%20issue) or [help wanted](https://github.com/dgraph-io/badger/labels/help%20wanted) labels. These issues are well suited for new contributors. + + +## Setting up your development environment + +Badger uses [`Go Modules`](https://github.com/golang/go/wiki/Modules) +to manage dependencies. The version of Go should be **1.12** or above. + +### Fork the project + +- Visit https://github.com/dgraph-io/badger +- Click the `Fork` button (top right) to create a fork of the repository + +### Clone the project + +```sh +$ git clone https://github.com/$GITHUB_USER/badger +$ cd badger +$ git remote add upstream git@github.com:dgraph-io/badger.git + +# Never push to the upstream master +git remote set-url --push upstream no_push +``` + +### New branch for a new code + +Get your local master up to date: + +```sh +$ git fetch upstream +$ git checkout master +$ git rebase upstream/master +``` + +Create a new branch from the master: + +```sh +$ git checkout -b my_new_feature +``` + +And now you can finally add your changes to project. + +### Test + +Build and run all tests: + +```sh +$ ./test.sh +``` + +### Commit and push + +Commit your changes: + +```sh +$ git commit +``` + +When the changes are ready to review: + +```sh +$ git push origin my_new_feature +``` + +### Create a Pull Request + +Just open `https://github.com/$GITHUB_USER/badger/pull/new/my_new_feature` and +fill the PR description. + +### Sign the CLA + +Click the **Sign in with Github to agree** button to sign the CLA. [An example](https://cla-assistant.io/dgraph-io/badger?pullRequest=1377). + +### Get a code review + +If your pull request (PR) is opened, it will be assigned to one or more +reviewers. Those reviewers will do a code review. + +To address review comments, you should commit the changes to the same branch of +the PR on your fork. diff --git a/vendor/github.com/dgraph-io/badger/v3/LICENSE b/vendor/github.com/dgraph-io/badger/v3/LICENSE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/vendor/github.com/dgraph-io/badger/v3/README.md b/vendor/github.com/dgraph-io/badger/v3/README.md new file mode 100644 index 0000000000..aef00efc05 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/README.md @@ -0,0 +1,203 @@ +# BadgerDB [![Go Reference](https://pkg.go.dev/badge/github.com/dgraph-io/badger/v3.svg)](https://pkg.go.dev/github.com/dgraph-io/badger/v3) [![Go Report Card](https://goreportcard.com/badge/github.com/dgraph-io/badger)](https://goreportcard.com/report/github.com/dgraph-io/badger) [![Sourcegraph](https://sourcegraph.com/github.com/dgraph-io/badger/-/badge.svg)](https://sourcegraph.com/github.com/dgraph-io/badger?badge) [![Build Status](https://teamcity.dgraph.io/guestAuth/app/rest/builds/buildType:(id:Badger_UnitTests)/statusIcon.svg)](https://teamcity.dgraph.io/viewLog.html?buildTypeId=Badger_UnitTests&buildId=lastFinished&guest=1) ![Appveyor](https://ci.appveyor.com/api/projects/status/github/dgraph-io/badger?branch=master&svg=true) [![Coverage Status](https://coveralls.io/repos/github/dgraph-io/badger/badge.svg?branch=master)](https://coveralls.io/github/dgraph-io/badger?branch=master) + +![Badger mascot](images/diggy-shadow.png) + +BadgerDB is an embeddable, persistent and fast key-value (KV) database written +in pure Go. It is the underlying database for [Dgraph](https://dgraph.io), a +fast, distributed graph database. It's meant to be a performant alternative to +non-Go-based key-value stores like RocksDB. + +**Use [Discuss Issues](https://discuss.dgraph.io/c/issues/badger/37) for reporting issues about this repository.** + +## Project Status [March 24, 2020] + +Badger is stable and is being used to serve data sets worth hundreds of +terabytes. Badger supports concurrent ACID transactions with serializable +snapshot isolation (SSI) guarantees. A Jepsen-style bank test runs nightly for +8h, with `--race` flag and ensures the maintenance of transactional guarantees. +Badger has also been tested to work with filesystem level anomalies, to ensure +persistence and consistency. Badger is being used by a number of projects which +includes Dgraph, Jaeger Tracing, UsenetExpress, and many more. + +The list of projects using Badger can be found [here](#projects-using-badger). + +Badger v1.0 was released in Nov 2017, and the latest version that is data-compatible +with v1.0 is v1.6.0. + +Badger v2.0 was released in Nov 2019 with a new storage format which won't +be compatible with all of the v1.x. Badger v2.0 supports compression, encryption and uses a cache to speed up lookup. + +The [Changelog] is kept fairly up-to-date. + +For more details on our version naming schema please read [Choosing a version](#choosing-a-version). + +[Changelog]:https://github.com/dgraph-io/badger/blob/master/CHANGELOG.md + +## Table of Contents + * [Getting Started](#getting-started) + + [Installing](#installing) + - [Installing Badger Command Line Tool](#installing-badger-command-line-tool) + - [Choosing a version](#choosing-a-version) + * [Badger Documentation](#badger-documentation) + * [Resources](#resources) + + [Blog Posts](#blog-posts) + * [Design](#design) + + [Comparisons](#comparisons) + + [Benchmarks](#benchmarks) + * [Projects Using Badger](#projects-using-badger) + * [Contributing](#contributing) + * [Contact](#contact) + +## Getting Started + +### Installing +To start using Badger, install Go 1.12 or above. Badger v2 needs go modules. Run the following command to retrieve the library. + +```sh +$ go get github.com/dgraph-io/badger/v3 +``` +This will retrieve the library. + +#### Installing Badger Command Line Tool + +Download and extract the latest Badger DB release from https://github.com/dgraph-io/badger/releases and then run the following commands. + +```sh +$ cd badger-/badger +$ go install +``` +This will install the badger command line utility into your $GOBIN path. + +#### Choosing a version + +BadgerDB is a pretty special package from the point of view that the most important change we can +make to it is not on its API but rather on how data is stored on disk. + +This is why we follow a version naming schema that differs from Semantic Versioning. + +- New major versions are released when the data format on disk changes in an incompatible way. +- New minor versions are released whenever the API changes but data compatibility is maintained. + Note that the changes on the API could be backward-incompatible - unlike Semantic Versioning. +- New patch versions are released when there's no changes to the data format nor the API. + +Following these rules: + +- v1.5.0 and v1.6.0 can be used on top of the same files without any concerns, as their major + version is the same, therefore the data format on disk is compatible. +- v1.6.0 and v2.0.0 are data incompatible as their major version implies, so files created with + v1.6.0 will need to be converted into the new format before they can be used by v2.0.0. + +For a longer explanation on the reasons behind using a new versioning naming schema, you can read +[VERSIONING.md](VERSIONING.md). + +## Badger Documentation + +Badger Documentation is available at https://dgraph.io/docs/badger + +## Resources + +### Blog Posts +1. [Introducing Badger: A fast key-value store written natively in +Go](https://open.dgraph.io/post/badger/) +2. [Make Badger crash resilient with ALICE](https://blog.dgraph.io/post/alice/) +3. [Badger vs LMDB vs BoltDB: Benchmarking key-value databases in Go](https://blog.dgraph.io/post/badger-lmdb-boltdb/) +4. [Concurrent ACID Transactions in Badger](https://blog.dgraph.io/post/badger-txn/) + +## Design +Badger was written with these design goals in mind: + +- Write a key-value database in pure Go. +- Use latest research to build the fastest KV database for data sets spanning terabytes. +- Optimize for SSDs. + +Badger’s design is based on a paper titled _[WiscKey: Separating Keys from +Values in SSD-conscious Storage][wisckey]_. + +[wisckey]: https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf + +### Comparisons +| Feature | Badger | RocksDB | BoltDB | +| ------- | ------ | ------- | ------ | +| Design | LSM tree with value log | LSM tree only | B+ tree | +| High Read throughput | Yes | No | Yes | +| High Write throughput | Yes | Yes | No | +| Designed for SSDs | Yes (with latest research 1) | Not specifically 2 | No | +| Embeddable | Yes | Yes | Yes | +| Sorted KV access | Yes | Yes | Yes | +| Pure Go (no Cgo) | Yes | No | Yes | +| Transactions | Yes, ACID, concurrent with SSI3 | Yes (but non-ACID) | Yes, ACID | +| Snapshots | Yes | Yes | Yes | +| TTL support | Yes | Yes | No | +| 3D access (key-value-version) | Yes4 | No | No | + +1 The [WISCKEY paper][wisckey] (on which Badger is based) saw big +wins with separating values from keys, significantly reducing the write +amplification compared to a typical LSM tree. + +2 RocksDB is an SSD optimized version of LevelDB, which was designed specifically for rotating disks. +As such RocksDB's design isn't aimed at SSDs. + +3 SSI: Serializable Snapshot Isolation. For more details, see the blog post [Concurrent ACID Transactions in Badger](https://blog.dgraph.io/post/badger-txn/) + +4 Badger provides direct access to value versions via its Iterator API. +Users can also specify how many versions to keep per key via Options. + +### Benchmarks +We have run comprehensive benchmarks against RocksDB, Bolt and LMDB. The +benchmarking code, and the detailed logs for the benchmarks can be found in the +[badger-bench] repo. More explanation, including graphs can be found the blog posts (linked +above). + +[badger-bench]: https://github.com/dgraph-io/badger-bench + +## Projects Using Badger +Below is a list of known projects that use Badger: + +* [Dgraph](https://github.com/dgraph-io/dgraph) - Distributed graph database. +* [Jaeger](https://github.com/jaegertracing/jaeger) - Distributed tracing platform. +* [go-ipfs](https://github.com/ipfs/go-ipfs) - Go client for the InterPlanetary File System (IPFS), a new hypermedia distribution protocol. +* [Riot](https://github.com/go-ego/riot) - An open-source, distributed search engine. +* [emitter](https://github.com/emitter-io/emitter) - Scalable, low latency, distributed pub/sub broker with message storage, uses MQTT, gossip and badger. +* [OctoSQL](https://github.com/cube2222/octosql) - Query tool that allows you to join, analyse and transform data from multiple databases using SQL. +* [Dkron](https://dkron.io/) - Distributed, fault tolerant job scheduling system. +* [smallstep/certificates](https://github.com/smallstep/certificates) - Step-ca is an online certificate authority for secure, automated certificate management. +* [Sandglass](https://github.com/celrenheit/sandglass) - distributed, horizontally scalable, persistent, time sorted message queue. +* [TalariaDB](https://github.com/grab/talaria) - Grab's Distributed, low latency time-series database. +* [Sloop](https://github.com/salesforce/sloop) - Salesforce's Kubernetes History Visualization Project. +* [Immudb](https://github.com/codenotary/immudb) - Lightweight, high-speed immutable database for systems and applications. +* [Usenet Express](https://usenetexpress.com/) - Serving over 300TB of data with Badger. +* [gorush](https://github.com/appleboy/gorush) - A push notification server written in Go. +* [Dispatch Protocol](https://github.com/dispatchlabs/disgo) - Blockchain protocol for distributed application data analytics. +* [GarageMQ](https://github.com/valinurovam/garagemq) - AMQP server written in Go. +* [RedixDB](https://alash3al.github.io/redix/) - A real-time persistent key-value store with the same redis protocol. +* [BBVA](https://github.com/BBVA/raft-badger) - Raft backend implementation using BadgerDB for Hashicorp raft. +* [Fantom](https://github.com/Fantom-foundation/go-lachesis) - aBFT Consensus platform for distributed applications. +* [decred](https://github.com/decred/dcrdata) - An open, progressive, and self-funding cryptocurrency with a system of community-based governance integrated into its blockchain. +* [OpenNetSys](https://github.com/opennetsys/c3-go) - Create useful dApps in any software language. +* [HoneyTrap](https://github.com/honeytrap/honeytrap) - An extensible and opensource system for running, monitoring and managing honeypots. +* [Insolar](https://github.com/insolar/insolar) - Enterprise-ready blockchain platform. +* [IoTeX](https://github.com/iotexproject/iotex-core) - The next generation of the decentralized network for IoT powered by scalability- and privacy-centric blockchains. +* [go-sessions](https://github.com/kataras/go-sessions) - The sessions manager for Go net/http and fasthttp. +* [Babble](https://github.com/mosaicnetworks/babble) - BFT Consensus platform for distributed applications. +* [Tormenta](https://github.com/jpincas/tormenta) - Embedded object-persistence layer / simple JSON database for Go projects. +* [BadgerHold](https://github.com/timshannon/badgerhold) - An embeddable NoSQL store for querying Go types built on Badger +* [Goblero](https://github.com/didil/goblero) - Pure Go embedded persistent job queue backed by BadgerDB +* [Surfline](https://www.surfline.com) - Serving global wave and weather forecast data with Badger. +* [Cete](https://github.com/mosuka/cete) - Simple and highly available distributed key-value store built on Badger. Makes it easy bringing up a cluster of Badger with Raft consensus algorithm by hashicorp/raft. +* [Volument](https://volument.com/) - A new take on website analytics backed by Badger. +* [KVdb](https://kvdb.io/) - Hosted key-value store and serverless platform built on top of Badger. +* [Terminotes](https://gitlab.com/asad-awadia/terminotes) - Self hosted notes storage and search server - storage powered by BadgerDB +* [Pyroscope](https://github.com/pyroscope-io/pyroscope) - Open source confinuous profiling platform built with BadgerDB +* [Veri](https://github.com/bgokden/veri) - A distributed feature store optimized for Search and Recommendation tasks. + +If you are using Badger in a project please send a pull request to add it to the list. + +## Contributing + +If you're interested in contributing to Badger see [CONTRIBUTING.md](./CONTRIBUTING.md). + +## Contact +- Please use [discuss.dgraph.io](https://discuss.dgraph.io) for questions, feature requests and discussions. +- Please use [Github issue tracker](https://github.com/dgraph-io/badger/issues) for filing bugs or feature requests. +- Follow us on Twitter [@dgraphlabs](https://twitter.com/dgraphlabs). + diff --git a/vendor/github.com/dgraph-io/badger/v3/VERSIONING.md b/vendor/github.com/dgraph-io/badger/v3/VERSIONING.md new file mode 100644 index 0000000000..a890a36ffb --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/VERSIONING.md @@ -0,0 +1,47 @@ +# Serialization Versioning: Semantic Versioning for databases + +Semantic Versioning, commonly known as SemVer, is a great idea that has been very widely adopted as +a way to decide how to name software versions. The whole concept is very well summarized on +semver.org with the following lines: + +> Given a version number MAJOR.MINOR.PATCH, increment the: +> +> 1. MAJOR version when you make incompatible API changes, +> 2. MINOR version when you add functionality in a backwards-compatible manner, and +> 3. PATCH version when you make backwards-compatible bug fixes. +> +> Additional labels for pre-release and build metadata are available as extensions to the +> MAJOR.MINOR.PATCH format. + +Unfortunately, API changes are not the most important changes for libraries that serialize data for +later consumption. For these libraries, such as BadgerDB, changes to the API are much easier to +handle than change to the data format used to store data on disk. + +## Serialization Version specification + +Serialization Versioning, like Semantic Versioning, uses 3 numbers and also calls them +MAJOR.MINOR.PATCH, but the semantics of the numbers are slightly modified: + +Given a version number MAJOR.MINOR.PATCH, increment the: + +- MAJOR version when you make changes that require a transformation of the dataset before it can be +used again. +- MINOR version when old datasets are still readable but the API might have changed in +backwards-compatible or incompatible ways. +- PATCH version when you make backwards-compatible bug fixes. + +Additional labels for pre-release and build metadata are available as extensions to the +MAJOR.MINOR.PATCH format. + +Following this naming strategy, migration from v1.x to v2.x requires a migration strategy for your +existing dataset, and as such has to be carefully planned. Migrations in between different minor +versions (e.g. v1.5.x and v1.6.x) might break your build, as the API *might* have changed, but once +your code compiles there's no need for any data migration. Lastly, changes in between two different +patch versions should never break your build or dataset. + +For more background on our decision to adopt Serialization Versioning, read the blog post +[Semantic Versioning, Go Modules, and Databases][blog] and the original proposal on +[this comment on Dgraph's Discuss forum][discuss]. + +[blog]: https://blog.dgraph.io/post/serialization-versioning/ +[discuss]: https://discuss.dgraph.io/t/go-modules-on-badger-and-dgraph/4662/7 \ No newline at end of file diff --git a/vendor/github.com/dgraph-io/badger/v3/appveyor.yml b/vendor/github.com/dgraph-io/badger/v3/appveyor.yml new file mode 100644 index 0000000000..1842b117bf --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/appveyor.yml @@ -0,0 +1,49 @@ +# version format +version: "{build}" + +# Operating system (build VM template) +os: Windows Server 2012 R2 + +# Platform. +platform: x64 + +clone_folder: c:\gopath\src\github.com\dgraph-io\badger + +# Environment variables +environment: + GOVERSION: 1.12 + GOPATH: c:\gopath + GO111MODULE: on + +# scripts that run after cloning repository +install: + - set PATH=%GOPATH%\bin;c:\go\bin;c:\msys64\mingw64\bin;%PATH% + - go version + - go env + - python --version + - gcc --version + +# To run your custom scripts instead of automatic MSBuild +build_script: + # We need to disable firewall - https://github.com/appveyor/ci/issues/1579#issuecomment-309830648 + - ps: Disable-NetFirewallRule -DisplayName 'File and Printer Sharing (SMB-Out)' + - cd c:\gopath\src\github.com\dgraph-io\badger + - git branch + - go get -t ./... + +# To run your custom scripts instead of automatic tests +test_script: + # Unit tests + - ps: Add-AppveyorTest "Unit Tests" -Outcome Running + - go test -v github.com/dgraph-io/badger/... + - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed + +notifications: + - provider: Email + to: + - pawan@dgraph.io + on_build_failure: true + on_build_status_changed: true +# to disable deployment +deploy: off + diff --git a/vendor/github.com/dgraph-io/badger/v3/backup.go b/vendor/github.com/dgraph-io/badger/v3/backup.go new file mode 100644 index 0000000000..42bdc1791e --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/backup.go @@ -0,0 +1,289 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bufio" + "bytes" + "context" + "encoding/binary" + "io" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// flushThreshold determines when a buffer will be flushed. When performing a +// backup/restore, the entries will be batched up until the total size of batch +// is more than flushThreshold or entry size (without the value size) is more +// than the maxBatchSize. +const flushThreshold = 100 << 20 + +// Backup dumps a protobuf-encoded list of all entries in the database into the +// given writer, that are newer than or equal to the specified version. It +// returns a timestamp (version) indicating the version of last entry that is +// dumped, which after incrementing by 1 can be passed into later invocation to +// generate incremental backup of entries that have been added/modified since +// the last invocation of DB.Backup(). +// DB.Backup is a wrapper function over Stream.Backup to generate full and +// incremental backups of the DB. For more control over how many goroutines are +// used to generate the backup, or if you wish to backup only a certain range +// of keys, use Stream.Backup directly. +func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) { + stream := db.NewStream() + stream.LogPrefix = "DB.Backup" + stream.SinceTs = since + return stream.Backup(w, since) +} + +// Backup dumps a protobuf-encoded list of all entries in the database into the +// given writer, that are newer than or equal to the specified version. It returns a +// timestamp(version) indicating the version of last entry that was dumped, which +// after incrementing by 1 can be passed into a later invocation to generate an +// incremental dump of entries that have been added/modified since the last +// invocation of Stream.Backup(). +// +// This can be used to backup the data in a database at a given point in time. +func (stream *Stream) Backup(w io.Writer, since uint64) (uint64, error) { + stream.KeyToList = func(key []byte, itr *Iterator) (*pb.KVList, error) { + list := &pb.KVList{} + a := itr.Alloc + for ; itr.Valid(); itr.Next() { + item := itr.Item() + if !bytes.Equal(item.Key(), key) { + return list, nil + } + if item.Version() < since { + return nil, errors.Errorf("Backup: Item Version: %d less than sinceTs: %d", + item.Version(), since) + } + + var valCopy []byte + if !item.IsDeletedOrExpired() { + // No need to copy value, if item is deleted or expired. + var err error + err = item.Value(func(val []byte) error { + valCopy = a.Copy(val) + return nil + }) + if err != nil { + stream.db.opt.Errorf("Key [%x, %d]. Error while fetching value [%v]\n", + item.Key(), item.Version(), err) + return nil, err + } + } + + // clear txn bits + meta := item.meta &^ (bitTxn | bitFinTxn) + kv := y.NewKV(a) + *kv = pb.KV{ + Key: a.Copy(item.Key()), + Value: valCopy, + UserMeta: a.Copy([]byte{item.UserMeta()}), + Version: item.Version(), + ExpiresAt: item.ExpiresAt(), + Meta: a.Copy([]byte{meta}), + } + list.Kv = append(list.Kv, kv) + + switch { + case item.DiscardEarlierVersions(): + // If we need to discard earlier versions of this item, add a delete + // marker just below the current version. + list.Kv = append(list.Kv, &pb.KV{ + Key: item.KeyCopy(nil), + Version: item.Version() - 1, + Meta: []byte{bitDelete}, + }) + return list, nil + + case item.IsDeletedOrExpired(): + return list, nil + } + } + return list, nil + } + + var maxVersion uint64 + stream.Send = func(buf *z.Buffer) error { + list, err := BufferToKVList(buf) + if err != nil { + return err + } + out := list.Kv[:0] + for _, kv := range list.Kv { + if maxVersion < kv.Version { + maxVersion = kv.Version + } + if !kv.StreamDone { + // Don't pick stream done changes. + out = append(out, kv) + } + } + list.Kv = out + return writeTo(list, w) + } + + if err := stream.Orchestrate(context.Background()); err != nil { + return 0, err + } + return maxVersion, nil +} + +func writeTo(list *pb.KVList, w io.Writer) error { + if err := binary.Write(w, binary.LittleEndian, uint64(proto.Size(list))); err != nil { + return err + } + buf, err := proto.Marshal(list) + if err != nil { + return err + } + _, err = w.Write(buf) + return err +} + +// KVLoader is used to write KVList objects in to badger. It can be used to restore a backup. +type KVLoader struct { + db *DB + throttle *y.Throttle + entries []*Entry + entriesSize int64 + totalSize int64 +} + +// NewKVLoader returns a new instance of KVLoader. +func (db *DB) NewKVLoader(maxPendingWrites int) *KVLoader { + return &KVLoader{ + db: db, + throttle: y.NewThrottle(maxPendingWrites), + entries: make([]*Entry, 0, db.opt.maxBatchCount), + } +} + +// Set writes the key-value pair to the database. +func (l *KVLoader) Set(kv *pb.KV) error { + var userMeta, meta byte + if len(kv.UserMeta) > 0 { + userMeta = kv.UserMeta[0] + } + if len(kv.Meta) > 0 { + meta = kv.Meta[0] + } + e := &Entry{ + Key: y.KeyWithTs(kv.Key, kv.Version), + Value: kv.Value, + UserMeta: userMeta, + ExpiresAt: kv.ExpiresAt, + meta: meta, + } + estimatedSize := e.estimateSizeAndSetThreshold(l.db.valueThreshold()) + // Flush entries if inserting the next entry would overflow the transactional limits. + if int64(len(l.entries))+1 >= l.db.opt.maxBatchCount || + l.entriesSize+estimatedSize >= l.db.opt.maxBatchSize || + l.totalSize >= flushThreshold { + if err := l.send(); err != nil { + return err + } + } + l.entries = append(l.entries, e) + l.entriesSize += estimatedSize + l.totalSize += estimatedSize + int64(len(e.Value)) + return nil +} + +func (l *KVLoader) send() error { + if err := l.throttle.Do(); err != nil { + return err + } + if err := l.db.batchSetAsync(l.entries, func(err error) { + l.throttle.Done(err) + }); err != nil { + return err + } + + l.entries = make([]*Entry, 0, l.db.opt.maxBatchCount) + l.entriesSize = 0 + l.totalSize = 0 + return nil +} + +// Finish is meant to be called after all the key-value pairs have been loaded. +func (l *KVLoader) Finish() error { + if len(l.entries) > 0 { + if err := l.send(); err != nil { + return err + } + } + return l.throttle.Finish() +} + +// Load reads a protobuf-encoded list of all entries from a reader and writes +// them to the database. This can be used to restore the database from a backup +// made by calling DB.Backup(). If more complex logic is needed to restore a badger +// backup, the KVLoader interface should be used instead. +// +// DB.Load() should be called on a database that is not running any other +// concurrent transactions while it is running. +func (db *DB) Load(r io.Reader, maxPendingWrites int) error { + br := bufio.NewReaderSize(r, 16<<10) + unmarshalBuf := make([]byte, 1<<10) + + ldr := db.NewKVLoader(maxPendingWrites) + for { + var sz uint64 + err := binary.Read(br, binary.LittleEndian, &sz) + if err == io.EOF { + break + } else if err != nil { + return err + } + + if cap(unmarshalBuf) < int(sz) { + unmarshalBuf = make([]byte, sz) + } + + if _, err = io.ReadFull(br, unmarshalBuf[:sz]); err != nil { + return err + } + + list := &pb.KVList{} + if err := proto.Unmarshal(unmarshalBuf[:sz], list); err != nil { + return err + } + + for _, kv := range list.Kv { + if err := ldr.Set(kv); err != nil { + return err + } + + // Update nextTxnTs, memtable stores this + // timestamp in badger head when flushed. + if kv.Version >= db.orc.nextTxnTs { + db.orc.nextTxnTs = kv.Version + 1 + } + } + } + + if err := ldr.Finish(); err != nil { + return err + } + db.orc.txnMark.Done(db.orc.nextTxnTs - 1) + return nil +} diff --git a/vendor/github.com/dgraph-io/badger/v3/batch.go b/vendor/github.com/dgraph-io/badger/v3/batch.go new file mode 100644 index 0000000000..a57022ea78 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/batch.go @@ -0,0 +1,245 @@ +/* + * Copyright 2018 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "sync" + "sync/atomic" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + "github.com/pkg/errors" +) + +// WriteBatch holds the necessary info to perform batched writes. +type WriteBatch struct { + sync.Mutex + txn *Txn + db *DB + throttle *y.Throttle + err atomic.Value + + isManaged bool + commitTs uint64 + finished bool +} + +// NewWriteBatch creates a new WriteBatch. This provides a way to conveniently do a lot of writes, +// batching them up as tightly as possible in a single transaction and using callbacks to avoid +// waiting for them to commit, thus achieving good performance. This API hides away the logic of +// creating and committing transactions. Due to the nature of SSI guaratees provided by Badger, +// blind writes can never encounter transaction conflicts (ErrConflict). +func (db *DB) NewWriteBatch() *WriteBatch { + if db.opt.managedTxns { + panic("cannot use NewWriteBatch in managed mode. Use NewWriteBatchAt instead") + } + return db.newWriteBatch(false) +} + +func (db *DB) newWriteBatch(isManaged bool) *WriteBatch { + return &WriteBatch{ + db: db, + isManaged: isManaged, + txn: db.newTransaction(true, isManaged), + throttle: y.NewThrottle(16), + } +} + +// SetMaxPendingTxns sets a limit on maximum number of pending transactions while writing batches. +// This function should be called before using WriteBatch. Default value of MaxPendingTxns is +// 16 to minimise memory usage. +func (wb *WriteBatch) SetMaxPendingTxns(max int) { + wb.throttle = y.NewThrottle(max) +} + +// Cancel function must be called if there's a chance that Flush might not get +// called. If neither Flush or Cancel is called, the transaction oracle would +// never get a chance to clear out the row commit timestamp map, thus causing an +// unbounded memory consumption. Typically, you can call Cancel as a defer +// statement right after NewWriteBatch is called. +// +// Note that any committed writes would still go through despite calling Cancel. +func (wb *WriteBatch) Cancel() { + wb.Lock() + defer wb.Unlock() + wb.finished = true + if err := wb.throttle.Finish(); err != nil { + wb.db.opt.Errorf("WatchBatch.Cancel error while finishing: %v", err) + } + wb.txn.Discard() +} + +func (wb *WriteBatch) callback(err error) { + // sync.WaitGroup is thread-safe, so it doesn't need to be run inside wb.Lock. + defer wb.throttle.Done(err) + if err == nil { + return + } + if err := wb.Error(); err != nil { + return + } + wb.err.Store(err) +} + +func (wb *WriteBatch) writeKV(kv *pb.KV) error { + e := Entry{Key: kv.Key, Value: kv.Value} + if len(kv.UserMeta) > 0 { + e.UserMeta = kv.UserMeta[0] + } + y.AssertTrue(kv.Version != 0) + e.version = kv.Version + return wb.handleEntry(&e) +} + +func (wb *WriteBatch) Write(buf *z.Buffer) error { + wb.Lock() + defer wb.Unlock() + + err := buf.SliceIterate(func(s []byte) error { + kv := &pb.KV{} + if err := kv.Unmarshal(s); err != nil { + return err + } + return wb.writeKV(kv) + }) + return err +} + +func (wb *WriteBatch) WriteList(kvList *pb.KVList) error { + wb.Lock() + defer wb.Unlock() + for _, kv := range kvList.Kv { + if err := wb.writeKV(kv); err != nil { + return err + } + } + return nil +} + +// SetEntryAt is the equivalent of Txn.SetEntry but it also allows setting version for the entry. +// SetEntryAt can be used only in managed mode. +func (wb *WriteBatch) SetEntryAt(e *Entry, ts uint64) error { + if !wb.db.opt.managedTxns { + return errors.New("SetEntryAt can only be used in managed mode. Use SetEntry instead") + } + e.version = ts + return wb.SetEntry(e) +} + +// Should be called with lock acquired. +func (wb *WriteBatch) handleEntry(e *Entry) error { + if err := wb.txn.SetEntry(e); err != ErrTxnTooBig { + return err + } + // Txn has reached it's zenith. Commit now. + if cerr := wb.commit(); cerr != nil { + return cerr + } + // This time the error must not be ErrTxnTooBig, otherwise, we make the + // error permanent. + if err := wb.txn.SetEntry(e); err != nil { + wb.err.Store(err) + return err + } + return nil +} + +// SetEntry is the equivalent of Txn.SetEntry. +func (wb *WriteBatch) SetEntry(e *Entry) error { + wb.Lock() + defer wb.Unlock() + return wb.handleEntry(e) +} + +// Set is equivalent of Txn.Set(). +func (wb *WriteBatch) Set(k, v []byte) error { + e := &Entry{Key: k, Value: v} + return wb.SetEntry(e) +} + +// DeleteAt is equivalent of Txn.Delete but accepts a delete timestamp. +func (wb *WriteBatch) DeleteAt(k []byte, ts uint64) error { + e := Entry{Key: k, meta: bitDelete, version: ts} + return wb.SetEntry(&e) +} + +// Delete is equivalent of Txn.Delete. +func (wb *WriteBatch) Delete(k []byte) error { + wb.Lock() + defer wb.Unlock() + + if err := wb.txn.Delete(k); err != ErrTxnTooBig { + return err + } + if err := wb.commit(); err != nil { + return err + } + if err := wb.txn.Delete(k); err != nil { + wb.err.Store(err) + return err + } + return nil +} + +// Caller to commit must hold a write lock. +func (wb *WriteBatch) commit() error { + if err := wb.Error(); err != nil { + return err + } + if wb.finished { + return y.ErrCommitAfterFinish + } + if err := wb.throttle.Do(); err != nil { + wb.err.Store(err) + return err + } + wb.txn.CommitWith(wb.callback) + wb.txn = wb.db.newTransaction(true, wb.isManaged) + wb.txn.commitTs = wb.commitTs + return wb.Error() +} + +// Flush must be called at the end to ensure that any pending writes get committed to Badger. Flush +// returns any error stored by WriteBatch. +func (wb *WriteBatch) Flush() error { + wb.Lock() + err := wb.commit() + if err != nil { + wb.Unlock() + return err + } + wb.finished = true + wb.txn.Discard() + wb.Unlock() + + if err := wb.throttle.Finish(); err != nil { + if wb.Error() != nil { + return errors.Errorf("wb.err: %s err: %s", wb.Error(), err) + } + return err + } + + return wb.Error() +} + +// Error returns any errors encountered so far. No commits would be run once an error is detected. +func (wb *WriteBatch) Error() error { + // If the interface conversion fails, the err will be nil. + err, _ := wb.err.Load().(error) + return err +} diff --git a/vendor/github.com/dgraph-io/badger/v3/changes.sh b/vendor/github.com/dgraph-io/badger/v3/changes.sh new file mode 100644 index 0000000000..e7cede9151 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/changes.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e +GHORG=${GHORG:-dgraph-io} +GHREPO=${GHREPO:-badger} +cat < 0 { + r.right = kr.right + } + if kr.inf { + r.inf = true + } +} + +func (r keyRange) overlapsWith(dst keyRange) bool { + // Empty keyRange always overlaps. + if r.isEmpty() { + return true + } + // TODO(ibrahim): Do you need this? + // Empty dst doesn't overlap with anything. + if dst.isEmpty() { + return false + } + if r.inf || dst.inf { + return true + } + + // [dst.left, dst.right] ... [r.left, r.right] + // If my left is greater than dst right, we have no overlap. + if y.CompareKeys(r.left, dst.right) > 0 { + return false + } + // [r.left, r.right] ... [dst.left, dst.right] + // If my right is less than dst left, we have no overlap. + if y.CompareKeys(r.right, dst.left) < 0 { + return false + } + // We have overlap. + return true +} + +// getKeyRange returns the smallest and the biggest in the list of tables. +// TODO(naman): Write a test for this. The smallest and the biggest should +// be the smallest of the leftmost table and the biggest of the right most table. +func getKeyRange(tables ...*table.Table) keyRange { + if len(tables) == 0 { + return keyRange{} + } + smallest := tables[0].Smallest() + biggest := tables[0].Biggest() + for i := 1; i < len(tables); i++ { + if y.CompareKeys(tables[i].Smallest(), smallest) < 0 { + smallest = tables[i].Smallest() + } + if y.CompareKeys(tables[i].Biggest(), biggest) > 0 { + biggest = tables[i].Biggest() + } + } + + // We pick all the versions of the smallest and the biggest key. Note that version zero would + // be the rightmost key, considering versions are default sorted in descending order. + return keyRange{ + left: y.KeyWithTs(y.ParseKey(smallest), math.MaxUint64), + right: y.KeyWithTs(y.ParseKey(biggest), 0), + } +} + +type levelCompactStatus struct { + ranges []keyRange + delSize int64 +} + +func (lcs *levelCompactStatus) debug() string { + var b bytes.Buffer + for _, r := range lcs.ranges { + b.WriteString(r.String()) + } + return b.String() +} + +func (lcs *levelCompactStatus) overlapsWith(dst keyRange) bool { + for _, r := range lcs.ranges { + if r.overlapsWith(dst) { + return true + } + } + return false +} + +func (lcs *levelCompactStatus) remove(dst keyRange) bool { + final := lcs.ranges[:0] + var found bool + for _, r := range lcs.ranges { + if !r.equals(dst) { + final = append(final, r) + } else { + found = true + } + } + lcs.ranges = final + return found +} + +type compactStatus struct { + sync.RWMutex + levels []*levelCompactStatus + tables map[uint64]struct{} +} + +func (cs *compactStatus) overlapsWith(level int, this keyRange) bool { + cs.RLock() + defer cs.RUnlock() + + thisLevel := cs.levels[level] + return thisLevel.overlapsWith(this) +} + +func (cs *compactStatus) delSize(l int) int64 { + cs.RLock() + defer cs.RUnlock() + return cs.levels[l].delSize +} + +type thisAndNextLevelRLocked struct{} + +// compareAndAdd will check whether we can run this compactDef. That it doesn't overlap with any +// other running compaction. If it can be run, it would store this run in the compactStatus state. +func (cs *compactStatus) compareAndAdd(_ thisAndNextLevelRLocked, cd compactDef) bool { + cs.Lock() + defer cs.Unlock() + + tl := cd.thisLevel.level + y.AssertTruef(tl < len(cs.levels), "Got level %d. Max levels: %d", tl, len(cs.levels)) + thisLevel := cs.levels[cd.thisLevel.level] + nextLevel := cs.levels[cd.nextLevel.level] + + if thisLevel.overlapsWith(cd.thisRange) { + return false + } + if nextLevel.overlapsWith(cd.nextRange) { + return false + } + // Check whether this level really needs compaction or not. Otherwise, we'll end up + // running parallel compactions for the same level. + // Update: We should not be checking size here. Compaction priority already did the size checks. + // Here we should just be executing the wish of others. + + thisLevel.ranges = append(thisLevel.ranges, cd.thisRange) + nextLevel.ranges = append(nextLevel.ranges, cd.nextRange) + thisLevel.delSize += cd.thisSize + for _, t := range append(cd.top, cd.bot...) { + cs.tables[t.ID()] = struct{}{} + } + return true +} + +func (cs *compactStatus) delete(cd compactDef) { + cs.Lock() + defer cs.Unlock() + + tl := cd.thisLevel.level + y.AssertTruef(tl < len(cs.levels), "Got level %d. Max levels: %d", tl, len(cs.levels)) + + thisLevel := cs.levels[cd.thisLevel.level] + nextLevel := cs.levels[cd.nextLevel.level] + + thisLevel.delSize -= cd.thisSize + found := thisLevel.remove(cd.thisRange) + // The following check makes sense only if we're compacting more than one + // table. In case of the max level, we might rewrite a single table to + // remove stale data. + if cd.thisLevel != cd.nextLevel && !cd.nextRange.isEmpty() { + found = nextLevel.remove(cd.nextRange) && found + } + + if !found { + this := cd.thisRange + next := cd.nextRange + fmt.Printf("Looking for: %s in this level %d.\n", this, tl) + fmt.Printf("This Level:\n%s\n", thisLevel.debug()) + fmt.Println() + fmt.Printf("Looking for: %s in next level %d.\n", next, cd.nextLevel.level) + fmt.Printf("Next Level:\n%s\n", nextLevel.debug()) + log.Fatal("keyRange not found") + } + for _, t := range append(cd.top, cd.bot...) { + _, ok := cs.tables[t.ID()] + y.AssertTrue(ok) + delete(cs.tables, t.ID()) + } +} diff --git a/vendor/github.com/dgraph-io/badger/v3/db.go b/vendor/github.com/dgraph-io/badger/v3/db.go new file mode 100644 index 0000000000..3ac6a2d959 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/db.go @@ -0,0 +1,2058 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bytes" + "context" + "encoding/binary" + "expvar" + "fmt" + "math" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/dgraph-io/badger/v3/options" + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/skl" + "github.com/dgraph-io/badger/v3/table" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto" + "github.com/dgraph-io/ristretto/z" + humanize "github.com/dustin/go-humanize" + "github.com/pkg/errors" +) + +var ( + badgerPrefix = []byte("!badger!") // Prefix for internal keys used by badger. + txnKey = []byte("!badger!txn") // For indicating end of entries in txn. + bannedNsKey = []byte("!badger!banned") // For storing the banned namespaces. +) + +const ( + maxNumSplits = 128 +) + +type closers struct { + updateSize *z.Closer + compactors *z.Closer + memtable *z.Closer + writes *z.Closer + valueGC *z.Closer + pub *z.Closer + cacheHealth *z.Closer +} + +type lockedKeys struct { + sync.RWMutex + keys map[uint64]struct{} +} + +func (lk *lockedKeys) add(key uint64) { + lk.Lock() + defer lk.Unlock() + lk.keys[key] = struct{}{} +} + +func (lk *lockedKeys) has(key uint64) bool { + lk.RLock() + defer lk.RUnlock() + _, ok := lk.keys[key] + return ok +} + +func (lk *lockedKeys) all() []uint64 { + lk.RLock() + defer lk.RUnlock() + keys := make([]uint64, 0, len(lk.keys)) + for key := range lk.keys { + keys = append(keys, key) + } + return keys +} + +// DB provides the various functions required to interact with Badger. +// DB is thread-safe. +type DB struct { + lock sync.RWMutex // Guards list of inmemory tables, not individual reads and writes. + + dirLockGuard *directoryLockGuard + // nil if Dir and ValueDir are the same + valueDirGuard *directoryLockGuard + + closers closers + + mt *memTable // Our latest (actively written) in-memory table + imm []*memTable // Add here only AFTER pushing to flushChan. + + // Initialized via openMemTables. + nextMemFid int + + opt Options + manifest *manifestFile + lc *levelsController + vlog valueLog + writeCh chan *request + flushChan chan flushTask // For flushing memtables. + closeOnce sync.Once // For closing DB only once. + + blockWrites int32 + isClosed uint32 + + orc *oracle + bannedNamespaces *lockedKeys + threshold *vlogThreshold + + pub *publisher + registry *KeyRegistry + blockCache *ristretto.Cache + indexCache *ristretto.Cache + allocPool *z.AllocatorPool +} + +const ( + kvWriteChCapacity = 1000 +) + +func checkAndSetOptions(opt *Options) error { + // It's okay to have zero compactors which will disable all compactions but + // we cannot have just one compactor otherwise we will end up with all data + // on level 2. + if opt.NumCompactors == 1 { + return errors.New("Cannot have 1 compactor. Need at least 2") + } + + if opt.InMemory && (opt.Dir != "" || opt.ValueDir != "") { + return errors.New("Cannot use badger in Disk-less mode with Dir or ValueDir set") + } + opt.maxBatchSize = (15 * opt.MemTableSize) / 100 + opt.maxBatchCount = opt.maxBatchSize / int64(skl.MaxNodeSize) + + // This is the maximum value, vlogThreshold can have if dynamic thresholding is enabled. + opt.maxValueThreshold = math.Min(maxValueThreshold, float64(opt.maxBatchSize)) + if opt.VLogPercentile < 0.0 || opt.VLogPercentile > 1.0 { + return errors.New("vlogPercentile must be within range of 0.0-1.0") + } + + // We are limiting opt.ValueThreshold to maxValueThreshold for now. + if opt.ValueThreshold > maxValueThreshold { + return errors.Errorf("Invalid ValueThreshold, must be less or equal to %d", + maxValueThreshold) + } + + // If ValueThreshold is greater than opt.maxBatchSize, we won't be able to push any data using + // the transaction APIs. Transaction batches entries into batches of size opt.maxBatchSize. + if opt.ValueThreshold > opt.maxBatchSize { + return errors.Errorf("Valuethreshold %d greater than max batch size of %d. Either "+ + "reduce opt.ValueThreshold or increase opt.MaxTableSize.", + opt.ValueThreshold, opt.maxBatchSize) + } + // ValueLogFileSize should be stricly LESS than 2<<30 otherwise we will + // overflow the uint32 when we mmap it in OpenMemtable. + if !(opt.ValueLogFileSize < 2<<30 && opt.ValueLogFileSize >= 1<<20) { + return ErrValueLogSize + } + + if opt.ReadOnly { + // Do not perform compaction in read only mode. + opt.CompactL0OnClose = false + } + + needCache := (opt.Compression != options.None) || (len(opt.EncryptionKey) > 0) + if needCache && opt.BlockCacheSize == 0 { + panic("BlockCacheSize should be set since compression/encryption are enabled") + } + return nil +} + +// Open returns a new DB object. +func Open(opt Options) (*DB, error) { + if err := checkAndSetOptions(&opt); err != nil { + return nil, err + } + var dirLockGuard, valueDirLockGuard *directoryLockGuard + + // Create directories and acquire lock on it only if badger is not running in InMemory mode. + // We don't have any directories/files in InMemory mode so we don't need to acquire + // any locks on them. + if !opt.InMemory { + if err := createDirs(opt); err != nil { + return nil, err + } + var err error + if !opt.BypassLockGuard { + dirLockGuard, err = acquireDirectoryLock(opt.Dir, lockFile, opt.ReadOnly) + if err != nil { + return nil, err + } + defer func() { + if dirLockGuard != nil { + _ = dirLockGuard.release() + } + }() + absDir, err := filepath.Abs(opt.Dir) + if err != nil { + return nil, err + } + absValueDir, err := filepath.Abs(opt.ValueDir) + if err != nil { + return nil, err + } + if absValueDir != absDir { + valueDirLockGuard, err = acquireDirectoryLock(opt.ValueDir, lockFile, opt.ReadOnly) + if err != nil { + return nil, err + } + defer func() { + if valueDirLockGuard != nil { + _ = valueDirLockGuard.release() + } + }() + } + } + } + + manifestFile, manifest, err := openOrCreateManifestFile(opt) + if err != nil { + return nil, err + } + defer func() { + if manifestFile != nil { + _ = manifestFile.close() + } + }() + + db := &DB{ + imm: make([]*memTable, 0, opt.NumMemtables), + flushChan: make(chan flushTask, opt.NumMemtables), + writeCh: make(chan *request, kvWriteChCapacity), + opt: opt, + manifest: manifestFile, + dirLockGuard: dirLockGuard, + valueDirGuard: valueDirLockGuard, + orc: newOracle(opt), + pub: newPublisher(), + allocPool: z.NewAllocatorPool(8), + bannedNamespaces: &lockedKeys{keys: make(map[uint64]struct{})}, + threshold: initVlogThreshold(&opt), + } + // Cleanup all the goroutines started by badger in case of an error. + defer func() { + if err != nil { + opt.Errorf("Received err: %v. Cleaning up...", err) + db.cleanup() + db = nil + } + }() + + if opt.BlockCacheSize > 0 { + numInCache := opt.BlockCacheSize / int64(opt.BlockSize) + if numInCache == 0 { + // Make the value of this variable at least one since the cache requires + // the number of counters to be greater than zero. + numInCache = 1 + } + + config := ristretto.Config{ + NumCounters: numInCache * 8, + MaxCost: opt.BlockCacheSize, + BufferItems: 64, + Metrics: true, + OnExit: table.BlockEvictHandler, + } + db.blockCache, err = ristretto.NewCache(&config) + if err != nil { + return nil, y.Wrap(err, "failed to create data cache") + } + } + + if opt.IndexCacheSize > 0 { + // Index size is around 5% of the table size. + indexSz := int64(float64(opt.MemTableSize) * 0.05) + numInCache := opt.IndexCacheSize / indexSz + if numInCache == 0 { + // Make the value of this variable at least one since the cache requires + // the number of counters to be greater than zero. + numInCache = 1 + } + + config := ristretto.Config{ + NumCounters: numInCache * 8, + MaxCost: opt.IndexCacheSize, + BufferItems: 64, + Metrics: true, + } + db.indexCache, err = ristretto.NewCache(&config) + if err != nil { + return nil, y.Wrap(err, "failed to create bf cache") + } + } + + db.closers.cacheHealth = z.NewCloser(1) + go db.monitorCache(db.closers.cacheHealth) + + if db.opt.InMemory { + db.opt.SyncWrites = false + // If badger is running in memory mode, push everything into the LSM Tree. + db.opt.ValueThreshold = math.MaxInt32 + } + krOpt := KeyRegistryOptions{ + ReadOnly: opt.ReadOnly, + Dir: opt.Dir, + EncryptionKey: opt.EncryptionKey, + EncryptionKeyRotationDuration: opt.EncryptionKeyRotationDuration, + InMemory: opt.InMemory, + } + + if db.registry, err = OpenKeyRegistry(krOpt); err != nil { + return db, err + } + db.calculateSize() + db.closers.updateSize = z.NewCloser(1) + go db.updateSize(db.closers.updateSize) + + if err := db.openMemTables(db.opt); err != nil { + return nil, y.Wrapf(err, "while opening memtables") + } + + if !db.opt.ReadOnly { + if db.mt, err = db.newMemTable(); err != nil { + return nil, y.Wrapf(err, "cannot create memtable") + } + } + + // newLevelsController potentially loads files in directory. + if db.lc, err = newLevelsController(db, &manifest); err != nil { + return db, err + } + + // Initialize vlog struct. + db.vlog.init(db) + + if !opt.ReadOnly { + db.closers.compactors = z.NewCloser(1) + db.lc.startCompact(db.closers.compactors) + + db.closers.memtable = z.NewCloser(1) + go func() { + _ = db.flushMemtable(db.closers.memtable) // Need levels controller to be up. + }() + // Flush them to disk asap. + for _, mt := range db.imm { + db.flushChan <- flushTask{mt: mt} + } + } + // We do increment nextTxnTs below. So, no need to do it here. + db.orc.nextTxnTs = db.MaxVersion() + db.opt.Infof("Set nextTxnTs to %d", db.orc.nextTxnTs) + + if err = db.vlog.open(db); err != nil { + return db, y.Wrapf(err, "During db.vlog.open") + } + + // Let's advance nextTxnTs to one more than whatever we observed via + // replaying the logs. + db.orc.txnMark.Done(db.orc.nextTxnTs) + // In normal mode, we must update readMark so older versions of keys can be removed during + // compaction when run in offline mode via the flatten tool. + db.orc.readMark.Done(db.orc.nextTxnTs) + db.orc.incrementNextTs() + + go db.threshold.listenForValueThresholdUpdate() + + if err := db.initBannedNamespaces(); err != nil { + return db, errors.Wrapf(err, "While setting banned keys") + } + + db.closers.writes = z.NewCloser(1) + go db.doWrites(db.closers.writes) + + if !db.opt.InMemory { + db.closers.valueGC = z.NewCloser(1) + go db.vlog.waitOnGC(db.closers.valueGC) + } + + db.closers.pub = z.NewCloser(1) + go db.pub.listenForUpdates(db.closers.pub) + + valueDirLockGuard = nil + dirLockGuard = nil + manifestFile = nil + return db, nil +} + +// initBannedNamespaces retrieves the banned namepsaces from the DB and updates in-memory structure. +func (db *DB) initBannedNamespaces() error { + if db.opt.NamespaceOffset < 0 { + return nil + } + return db.View(func(txn *Txn) error { + iopts := DefaultIteratorOptions + iopts.Prefix = bannedNsKey + iopts.PrefetchValues = false + iopts.InternalAccess = true + itr := txn.NewIterator(iopts) + defer itr.Close() + for itr.Rewind(); itr.Valid(); itr.Next() { + key := y.BytesToU64(itr.Item().Key()[len(bannedNsKey):]) + db.bannedNamespaces.add(key) + } + return nil + }) +} + +func (db *DB) MaxVersion() uint64 { + var maxVersion uint64 + update := func(a uint64) { + if a > maxVersion { + maxVersion = a + } + } + db.lock.Lock() + // In read only mode, we do not create new mem table. + if !db.opt.ReadOnly { + update(db.mt.maxVersion) + } + for _, mt := range db.imm { + update(mt.maxVersion) + } + db.lock.Unlock() + for _, ti := range db.Tables() { + update(ti.MaxVersion) + } + return maxVersion +} + +func (db *DB) monitorCache(c *z.Closer) { + defer c.Done() + count := 0 + analyze := func(name string, metrics *ristretto.Metrics) { + // If the mean life expectancy is less than 10 seconds, the cache + // might be too small. + le := metrics.LifeExpectancySeconds() + if le == nil { + return + } + lifeTooShort := le.Count > 0 && float64(le.Sum)/float64(le.Count) < 10 + hitRatioTooLow := metrics.Ratio() > 0 && metrics.Ratio() < 0.4 + if lifeTooShort && hitRatioTooLow { + db.opt.Warningf("%s might be too small. Metrics: %s\n", name, metrics) + db.opt.Warningf("Cache life expectancy (in seconds): %+v\n", le) + + } else if le.Count > 1000 && count%5 == 0 { + db.opt.Infof("%s metrics: %s\n", name, metrics) + } + } + + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + for { + select { + case <-c.HasBeenClosed(): + return + case <-ticker.C: + } + + analyze("Block cache", db.BlockCacheMetrics()) + analyze("Index cache", db.IndexCacheMetrics()) + count++ + } +} + +// cleanup stops all the goroutines started by badger. This is used in open to +// cleanup goroutines in case of an error. +func (db *DB) cleanup() { + db.stopMemoryFlush() + db.stopCompactions() + + db.blockCache.Close() + db.indexCache.Close() + if db.closers.updateSize != nil { + db.closers.updateSize.Signal() + } + if db.closers.valueGC != nil { + db.closers.valueGC.Signal() + } + if db.closers.writes != nil { + db.closers.writes.Signal() + } + if db.closers.pub != nil { + db.closers.pub.Signal() + } + + db.orc.Stop() + + // Do not use vlog.Close() here. vlog.Close truncates the files. We don't + // want to truncate files unless the user has specified the truncate flag. +} + +// BlockCacheMetrics returns the metrics for the underlying block cache. +func (db *DB) BlockCacheMetrics() *ristretto.Metrics { + if db.blockCache != nil { + return db.blockCache.Metrics + } + return nil +} + +// IndexCacheMetrics returns the metrics for the underlying index cache. +func (db *DB) IndexCacheMetrics() *ristretto.Metrics { + if db.indexCache != nil { + return db.indexCache.Metrics + } + return nil +} + +// Close closes a DB. It's crucial to call it to ensure all the pending updates make their way to +// disk. Calling DB.Close() multiple times would still only close the DB once. +func (db *DB) Close() error { + var err error + db.closeOnce.Do(func() { + err = db.close() + }) + return err +} + +// IsClosed denotes if the badger DB is closed or not. A DB instance should not +// be used after closing it. +func (db *DB) IsClosed() bool { + return atomic.LoadUint32(&db.isClosed) == 1 +} + +func (db *DB) close() (err error) { + defer db.allocPool.Release() + + db.opt.Debugf("Closing database") + db.opt.Infof("Lifetime L0 stalled for: %s\n", time.Duration(atomic.LoadInt64(&db.lc.l0stallsMs))) + + atomic.StoreInt32(&db.blockWrites, 1) + + if !db.opt.InMemory { + // Stop value GC first. + db.closers.valueGC.SignalAndWait() + } + + // Stop writes next. + db.closers.writes.SignalAndWait() + + // Don't accept any more write. + close(db.writeCh) + + db.closers.pub.SignalAndWait() + db.closers.cacheHealth.Signal() + + // Make sure that block writer is done pushing stuff into memtable! + // Otherwise, you will have a race condition: we are trying to flush memtables + // and remove them completely, while the block / memtable writer is still + // trying to push stuff into the memtable. This will also resolve the value + // offset problem: as we push into memtable, we update value offsets there. + if db.mt != nil { + if db.mt.sl.Empty() { + // Remove the memtable if empty. + db.mt.DecrRef() + } else { + db.opt.Debugf("Flushing memtable") + for { + pushedFlushTask := func() bool { + db.lock.Lock() + defer db.lock.Unlock() + y.AssertTrue(db.mt != nil) + select { + case db.flushChan <- flushTask{mt: db.mt}: + db.imm = append(db.imm, db.mt) // Flusher will attempt to remove this from s.imm. + db.mt = nil // Will segfault if we try writing! + db.opt.Debugf("pushed to flush chan\n") + return true + default: + // If we fail to push, we need to unlock and wait for a short while. + // The flushing operation needs to update s.imm. Otherwise, we have a + // deadlock. + // TODO: Think about how to do this more cleanly, maybe without any locks. + } + return false + }() + if pushedFlushTask { + break + } + time.Sleep(10 * time.Millisecond) + } + } + } + db.stopMemoryFlush() + db.stopCompactions() + + // Force Compact L0 + // We don't need to care about cstatus since no parallel compaction is running. + if db.opt.CompactL0OnClose { + err := db.lc.doCompact(173, compactionPriority{level: 0, score: 1.73}) + switch err { + case errFillTables: + // This error only means that there might be enough tables to do a compaction. So, we + // should not report it to the end user to avoid confusing them. + case nil: + db.opt.Debugf("Force compaction on level 0 done") + default: + db.opt.Warningf("While forcing compaction on level 0: %v", err) + } + } + + // Now close the value log. + if vlogErr := db.vlog.Close(); vlogErr != nil { + err = y.Wrap(vlogErr, "DB.Close") + } + + db.opt.Infof(db.LevelsToString()) + if lcErr := db.lc.close(); err == nil { + err = y.Wrap(lcErr, "DB.Close") + } + db.opt.Debugf("Waiting for closer") + db.closers.updateSize.SignalAndWait() + db.orc.Stop() + db.blockCache.Close() + db.indexCache.Close() + + atomic.StoreUint32(&db.isClosed, 1) + db.threshold.close() + + if db.opt.InMemory { + return + } + + if db.dirLockGuard != nil { + if guardErr := db.dirLockGuard.release(); err == nil { + err = y.Wrap(guardErr, "DB.Close") + } + } + if db.valueDirGuard != nil { + if guardErr := db.valueDirGuard.release(); err == nil { + err = y.Wrap(guardErr, "DB.Close") + } + } + if manifestErr := db.manifest.close(); err == nil { + err = y.Wrap(manifestErr, "DB.Close") + } + if registryErr := db.registry.Close(); err == nil { + err = y.Wrap(registryErr, "DB.Close") + } + + // Fsync directories to ensure that lock file, and any other removed files whose directory + // we haven't specifically fsynced, are guaranteed to have their directory entry removal + // persisted to disk. + if syncErr := db.syncDir(db.opt.Dir); err == nil { + err = y.Wrap(syncErr, "DB.Close") + } + if syncErr := db.syncDir(db.opt.ValueDir); err == nil { + err = y.Wrap(syncErr, "DB.Close") + } + + return err +} + +// VerifyChecksum verifies checksum for all tables on all levels. +// This method can be used to verify checksum, if opt.ChecksumVerificationMode is NoVerification. +func (db *DB) VerifyChecksum() error { + return db.lc.verifyChecksum() +} + +const ( + lockFile = "LOCK" +) + +// Sync syncs database content to disk. This function provides +// more control to user to sync data whenever required. +func (db *DB) Sync() error { + return db.vlog.sync() +} + +// getMemtables returns the current memtables and get references. +func (db *DB) getMemTables() ([]*memTable, func()) { + db.lock.RLock() + defer db.lock.RUnlock() + + var tables []*memTable + + // Mutable memtable does not exist in read-only mode. + if !db.opt.ReadOnly { + // Get mutable memtable. + tables = append(tables, db.mt) + db.mt.IncrRef() + } + + // Get immutable memtables. + last := len(db.imm) - 1 + for i := range db.imm { + tables = append(tables, db.imm[last-i]) + db.imm[last-i].IncrRef() + } + return tables, func() { + for _, tbl := range tables { + tbl.DecrRef() + } + } +} + +// get returns the value in memtable or disk for given key. +// Note that value will include meta byte. +// +// IMPORTANT: We should never write an entry with an older timestamp for the same key, We need to +// maintain this invariant to search for the latest value of a key, or else we need to search in all +// tables and find the max version among them. To maintain this invariant, we also need to ensure +// that all versions of a key are always present in the same table from level 1, because compaction +// can push any table down. +// +// Update(23/09/2020) - We have dropped the move key implementation. Earlier we +// were inserting move keys to fix the invalid value pointers but we no longer +// do that. For every get("fooX") call where X is the version, we will search +// for "fooX" in all the levels of the LSM tree. This is expensive but it +// removes the overhead of handling move keys completely. +func (db *DB) get(key []byte) (y.ValueStruct, error) { + if db.IsClosed() { + return y.ValueStruct{}, ErrDBClosed + } + tables, decr := db.getMemTables() // Lock should be released. + defer decr() + + var maxVs y.ValueStruct + version := y.ParseTs(key) + + y.NumGetsAdd(db.opt.MetricsEnabled, 1) + for i := 0; i < len(tables); i++ { + vs := tables[i].sl.Get(key) + y.NumMemtableGetsAdd(db.opt.MetricsEnabled, 1) + if vs.Meta == 0 && vs.Value == nil { + continue + } + // Found the required version of the key, return immediately. + if vs.Version == version { + return vs, nil + } + if maxVs.Version < vs.Version { + maxVs = vs + } + } + return db.lc.get(key, maxVs, 0) +} + +var requestPool = sync.Pool{ + New: func() interface{} { + return new(request) + }, +} + +func (db *DB) writeToLSM(b *request) error { + // We should check the length of b.Prts and b.Entries only when badger is not + // running in InMemory mode. In InMemory mode, we don't write anything to the + // value log and that's why the length of b.Ptrs will always be zero. + if !db.opt.InMemory && len(b.Ptrs) != len(b.Entries) { + return errors.Errorf("Ptrs and Entries don't match: %+v", b) + } + + for i, entry := range b.Entries { + var err error + if entry.skipVlogAndSetThreshold(db.valueThreshold()) { + // Will include deletion / tombstone case. + err = db.mt.Put(entry.Key, + y.ValueStruct{ + Value: entry.Value, + // Ensure value pointer flag is removed. Otherwise, the value will fail + // to be retrieved during iterator prefetch. `bitValuePointer` is only + // known to be set in write to LSM when the entry is loaded from a backup + // with lower ValueThreshold and its value was stored in the value log. + Meta: entry.meta &^ bitValuePointer, + UserMeta: entry.UserMeta, + ExpiresAt: entry.ExpiresAt, + }) + } else { + // Write pointer to Memtable. + err = db.mt.Put(entry.Key, + y.ValueStruct{ + Value: b.Ptrs[i].Encode(), + Meta: entry.meta | bitValuePointer, + UserMeta: entry.UserMeta, + ExpiresAt: entry.ExpiresAt, + }) + } + if err != nil { + return y.Wrapf(err, "while writing to memTable") + } + } + if db.opt.SyncWrites { + return db.mt.SyncWAL() + } + return nil +} + +// writeRequests is called serially by only one goroutine. +func (db *DB) writeRequests(reqs []*request) error { + if len(reqs) == 0 { + return nil + } + + done := func(err error) { + for _, r := range reqs { + r.Err = err + r.Wg.Done() + } + } + db.opt.Debugf("writeRequests called. Writing to value log") + err := db.vlog.write(reqs) + if err != nil { + done(err) + return err + } + + db.opt.Debugf("Sending updates to subscribers") + db.pub.sendUpdates(reqs) + db.opt.Debugf("Writing to memtable") + var count int + for _, b := range reqs { + if len(b.Entries) == 0 { + continue + } + count += len(b.Entries) + var i uint64 + for err = db.ensureRoomForWrite(); err == errNoRoom; err = db.ensureRoomForWrite() { + i++ + if i%100 == 0 { + db.opt.Debugf("Making room for writes") + } + // We need to poll a bit because both hasRoomForWrite and the flusher need access to s.imm. + // When flushChan is full and you are blocked there, and the flusher is trying to update s.imm, + // you will get a deadlock. + time.Sleep(10 * time.Millisecond) + } + if err != nil { + done(err) + return y.Wrap(err, "writeRequests") + } + if err := db.writeToLSM(b); err != nil { + done(err) + return y.Wrap(err, "writeRequests") + } + } + done(nil) + db.opt.Debugf("%d entries written", count) + return nil +} + +func (db *DB) sendToWriteCh(entries []*Entry) (*request, error) { + if atomic.LoadInt32(&db.blockWrites) == 1 { + return nil, ErrBlockedWrites + } + var count, size int64 + for _, e := range entries { + size += e.estimateSizeAndSetThreshold(db.valueThreshold()) + count++ + } + if count >= db.opt.maxBatchCount || size >= db.opt.maxBatchSize { + return nil, ErrTxnTooBig + } + + // We can only service one request because we need each txn to be stored in a contigous section. + // Txns should not interleave among other txns or rewrites. + req := requestPool.Get().(*request) + req.reset() + req.Entries = entries + req.Wg.Add(1) + req.IncrRef() // for db write + db.writeCh <- req // Handled in doWrites. + y.NumPutsAdd(db.opt.MetricsEnabled, int64(len(entries))) + + return req, nil +} + +func (db *DB) doWrites(lc *z.Closer) { + defer lc.Done() + pendingCh := make(chan struct{}, 1) + + writeRequests := func(reqs []*request) { + if err := db.writeRequests(reqs); err != nil { + db.opt.Errorf("writeRequests: %v", err) + } + <-pendingCh + } + + // This variable tracks the number of pending writes. + reqLen := new(expvar.Int) + y.PendingWritesSet(db.opt.MetricsEnabled, db.opt.Dir, reqLen) + + reqs := make([]*request, 0, 10) + for { + var r *request + select { + case r = <-db.writeCh: + case <-lc.HasBeenClosed(): + goto closedCase + } + + for { + reqs = append(reqs, r) + reqLen.Set(int64(len(reqs))) + + if len(reqs) >= 3*kvWriteChCapacity { + pendingCh <- struct{}{} // blocking. + goto writeCase + } + + select { + // Either push to pending, or continue to pick from writeCh. + case r = <-db.writeCh: + case pendingCh <- struct{}{}: + goto writeCase + case <-lc.HasBeenClosed(): + goto closedCase + } + } + + closedCase: + // All the pending request are drained. + // Don't close the writeCh, because it has be used in several places. + for { + select { + case r = <-db.writeCh: + reqs = append(reqs, r) + default: + pendingCh <- struct{}{} // Push to pending before doing a write. + writeRequests(reqs) + return + } + } + + writeCase: + go writeRequests(reqs) + reqs = make([]*request, 0, 10) + reqLen.Set(0) + } +} + +// batchSet applies a list of badger.Entry. If a request level error occurs it +// will be returned. +// Check(kv.BatchSet(entries)) +func (db *DB) batchSet(entries []*Entry) error { + req, err := db.sendToWriteCh(entries) + if err != nil { + return err + } + + return req.Wait() +} + +// batchSetAsync is the asynchronous version of batchSet. It accepts a callback +// function which is called when all the sets are complete. If a request level +// error occurs, it will be passed back via the callback. +// err := kv.BatchSetAsync(entries, func(err error)) { +// Check(err) +// } +func (db *DB) batchSetAsync(entries []*Entry, f func(error)) error { + req, err := db.sendToWriteCh(entries) + if err != nil { + return err + } + go func() { + err := req.Wait() + // Write is complete. Let's call the callback function now. + f(err) + }() + return nil +} + +var errNoRoom = errors.New("No room for write") + +// ensureRoomForWrite is always called serially. +func (db *DB) ensureRoomForWrite() error { + var err error + db.lock.Lock() + defer db.lock.Unlock() + + y.AssertTrue(db.mt != nil) // A nil mt indicates that DB is being closed. + if !db.mt.isFull() { + return nil + } + + select { + case db.flushChan <- flushTask{mt: db.mt}: + db.opt.Debugf("Flushing memtable, mt.size=%d size of flushChan: %d\n", + db.mt.sl.MemSize(), len(db.flushChan)) + // We manage to push this task. Let's modify imm. + db.imm = append(db.imm, db.mt) + db.mt, err = db.newMemTable() + if err != nil { + return y.Wrapf(err, "cannot create new mem table") + } + // New memtable is empty. We certainly have room. + return nil + default: + // We need to do this to unlock and allow the flusher to modify imm. + return errNoRoom + } +} + +func arenaSize(opt Options) int64 { + return opt.MemTableSize + opt.maxBatchSize + opt.maxBatchCount*int64(skl.MaxNodeSize) +} + +// buildL0Table builds a new table from the memtable. +func buildL0Table(ft flushTask, bopts table.Options) *table.Builder { + iter := ft.mt.sl.NewIterator() + defer iter.Close() + b := table.NewTableBuilder(bopts) + for iter.SeekToFirst(); iter.Valid(); iter.Next() { + if len(ft.dropPrefixes) > 0 && hasAnyPrefixes(iter.Key(), ft.dropPrefixes) { + continue + } + vs := iter.Value() + var vp valuePointer + if vs.Meta&bitValuePointer > 0 { + vp.Decode(vs.Value) + } + b.Add(iter.Key(), iter.Value(), vp.Len) + } + return b +} + +type flushTask struct { + mt *memTable + dropPrefixes [][]byte +} + +// handleFlushTask must be run serially. +func (db *DB) handleFlushTask(ft flushTask) error { + // There can be a scenario, when empty memtable is flushed. + if ft.mt.sl.Empty() { + return nil + } + + bopts := buildTableOptions(db) + builder := buildL0Table(ft, bopts) + defer builder.Close() + + // buildL0Table can return nil if the none of the items in the skiplist are + // added to the builder. This can happen when drop prefix is set and all + // the items are skipped. + if builder.Empty() { + builder.Finish() + return nil + } + + fileID := db.lc.reserveFileID() + var tbl *table.Table + var err error + if db.opt.InMemory { + data := builder.Finish() + tbl, err = table.OpenInMemoryTable(data, fileID, &bopts) + } else { + tbl, err = table.CreateTable(table.NewFilename(fileID, db.opt.Dir), builder) + } + if err != nil { + return y.Wrap(err, "error while creating table") + } + // We own a ref on tbl. + err = db.lc.addLevel0Table(tbl) // This will incrRef + _ = tbl.DecrRef() // Releases our ref. + return err +} + +// flushMemtable must keep running until we send it an empty flushTask. If there +// are errors during handling the flush task, we'll retry indefinitely. +func (db *DB) flushMemtable(lc *z.Closer) error { + defer lc.Done() + + for ft := range db.flushChan { + if ft.mt == nil { + // We close db.flushChan now, instead of sending a nil ft.mt. + continue + } + for { + err := db.handleFlushTask(ft) + if err == nil { + // Update s.imm. Need a lock. + db.lock.Lock() + // This is a single-threaded operation. ft.mt corresponds to the head of + // db.imm list. Once we flush it, we advance db.imm. The next ft.mt + // which would arrive here would match db.imm[0], because we acquire a + // lock over DB when pushing to flushChan. + // TODO: This logic is dirty AF. Any change and this could easily break. + y.AssertTrue(ft.mt == db.imm[0]) + db.imm = db.imm[1:] + ft.mt.DecrRef() // Return memory. + db.lock.Unlock() + + break + } + // Encountered error. Retry indefinitely. + db.opt.Errorf("Failure while flushing memtable to disk: %v. Retrying...\n", err) + time.Sleep(time.Second) + } + } + return nil +} + +func exists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return true, err +} + +// This function does a filewalk, calculates the size of vlog and sst files and stores it in +// y.LSMSize and y.VlogSize. +func (db *DB) calculateSize() { + if db.opt.InMemory { + return + } + newInt := func(val int64) *expvar.Int { + v := new(expvar.Int) + v.Add(val) + return v + } + + totalSize := func(dir string) (int64, int64) { + var lsmSize, vlogSize int64 + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + ext := filepath.Ext(path) + switch ext { + case ".sst": + lsmSize += info.Size() + case ".vlog": + vlogSize += info.Size() + } + return nil + }) + if err != nil { + db.opt.Debugf("Got error while calculating total size of directory: %s", dir) + } + return lsmSize, vlogSize + } + + lsmSize, vlogSize := totalSize(db.opt.Dir) + y.LSMSizeSet(db.opt.MetricsEnabled, db.opt.Dir, newInt(lsmSize)) + // If valueDir is different from dir, we'd have to do another walk. + if db.opt.ValueDir != db.opt.Dir { + _, vlogSize = totalSize(db.opt.ValueDir) + } + y.VlogSizeSet(db.opt.MetricsEnabled, db.opt.ValueDir, newInt(vlogSize)) +} + +func (db *DB) updateSize(lc *z.Closer) { + defer lc.Done() + if db.opt.InMemory { + return + } + + metricsTicker := time.NewTicker(time.Minute) + defer metricsTicker.Stop() + + for { + select { + case <-metricsTicker.C: + db.calculateSize() + case <-lc.HasBeenClosed(): + return + } + } +} + +// RunValueLogGC triggers a value log garbage collection. +// +// It picks value log files to perform GC based on statistics that are collected +// during compactions. If no such statistics are available, then log files are +// picked in random order. The process stops as soon as the first log file is +// encountered which does not result in garbage collection. +// +// When a log file is picked, it is first sampled. If the sample shows that we +// can discard at least discardRatio space of that file, it would be rewritten. +// +// If a call to RunValueLogGC results in no rewrites, then an ErrNoRewrite is +// thrown indicating that the call resulted in no file rewrites. +// +// We recommend setting discardRatio to 0.5, thus indicating that a file be +// rewritten if half the space can be discarded. This results in a lifetime +// value log write amplification of 2 (1 from original write + 0.5 rewrite + +// 0.25 + 0.125 + ... = 2). Setting it to higher value would result in fewer +// space reclaims, while setting it to a lower value would result in more space +// reclaims at the cost of increased activity on the LSM tree. discardRatio +// must be in the range (0.0, 1.0), both endpoints excluded, otherwise an +// ErrInvalidRequest is returned. +// +// Only one GC is allowed at a time. If another value log GC is running, or DB +// has been closed, this would return an ErrRejected. +// +// Note: Every time GC is run, it would produce a spike of activity on the LSM +// tree. +func (db *DB) RunValueLogGC(discardRatio float64) error { + if db.opt.InMemory { + return ErrGCInMemoryMode + } + if discardRatio >= 1.0 || discardRatio <= 0.0 { + return ErrInvalidRequest + } + + // Pick a log file and run GC + return db.vlog.runGC(discardRatio) +} + +// Size returns the size of lsm and value log files in bytes. It can be used to decide how often to +// call RunValueLogGC. +func (db *DB) Size() (lsm, vlog int64) { + if y.LSMSizeGet(db.opt.MetricsEnabled, db.opt.Dir) == nil { + lsm, vlog = 0, 0 + return + } + lsm = y.LSMSizeGet(db.opt.MetricsEnabled, db.opt.Dir).(*expvar.Int).Value() + vlog = y.VlogSizeGet(db.opt.MetricsEnabled, db.opt.ValueDir).(*expvar.Int).Value() + return +} + +// Sequence represents a Badger sequence. +type Sequence struct { + lock sync.Mutex + db *DB + key []byte + next uint64 + leased uint64 + bandwidth uint64 +} + +// Next would return the next integer in the sequence, updating the lease by running a transaction +// if needed. +func (seq *Sequence) Next() (uint64, error) { + seq.lock.Lock() + defer seq.lock.Unlock() + if seq.next >= seq.leased { + if err := seq.updateLease(); err != nil { + return 0, err + } + } + val := seq.next + seq.next++ + return val, nil +} + +// Release the leased sequence to avoid wasted integers. This should be done right +// before closing the associated DB. However it is valid to use the sequence after +// it was released, causing a new lease with full bandwidth. +func (seq *Sequence) Release() error { + seq.lock.Lock() + defer seq.lock.Unlock() + err := seq.db.Update(func(txn *Txn) error { + item, err := txn.Get(seq.key) + if err != nil { + return err + } + + var num uint64 + if err := item.Value(func(v []byte) error { + num = binary.BigEndian.Uint64(v) + return nil + }); err != nil { + return err + } + + if num == seq.leased { + var buf [8]byte + binary.BigEndian.PutUint64(buf[:], seq.next) + return txn.SetEntry(NewEntry(seq.key, buf[:])) + } + + return nil + }) + if err != nil { + return err + } + seq.leased = seq.next + return nil +} + +func (seq *Sequence) updateLease() error { + return seq.db.Update(func(txn *Txn) error { + item, err := txn.Get(seq.key) + switch { + case err == ErrKeyNotFound: + seq.next = 0 + case err != nil: + return err + default: + var num uint64 + if err := item.Value(func(v []byte) error { + num = binary.BigEndian.Uint64(v) + return nil + }); err != nil { + return err + } + seq.next = num + } + + lease := seq.next + seq.bandwidth + var buf [8]byte + binary.BigEndian.PutUint64(buf[:], lease) + if err = txn.SetEntry(NewEntry(seq.key, buf[:])); err != nil { + return err + } + seq.leased = lease + return nil + }) +} + +// GetSequence would initiate a new sequence object, generating it from the stored lease, if +// available, in the database. Sequence can be used to get a list of monotonically increasing +// integers. Multiple sequences can be created by providing different keys. Bandwidth sets the +// size of the lease, determining how many Next() requests can be served from memory. +// +// GetSequence is not supported on ManagedDB. Calling this would result in a panic. +func (db *DB) GetSequence(key []byte, bandwidth uint64) (*Sequence, error) { + if db.opt.managedTxns { + panic("Cannot use GetSequence with managedDB=true.") + } + + switch { + case len(key) == 0: + return nil, ErrEmptyKey + case bandwidth == 0: + return nil, ErrZeroBandwidth + } + seq := &Sequence{ + db: db, + key: key, + next: 0, + leased: 0, + bandwidth: bandwidth, + } + err := seq.updateLease() + return seq, err +} + +// Tables gets the TableInfo objects from the level controller. If withKeysCount +// is true, TableInfo objects also contain counts of keys for the tables. +func (db *DB) Tables() []TableInfo { + return db.lc.getTableInfo() +} + +// Levels gets the LevelInfo. +func (db *DB) Levels() []LevelInfo { + return db.lc.getLevelInfo() +} + +// EstimateSize can be used to get rough estimate of data size for a given prefix. +func (db *DB) EstimateSize(prefix []byte) (uint64, uint64) { + var onDiskSize, uncompressedSize uint64 + tables := db.Tables() + for _, ti := range tables { + if bytes.HasPrefix(ti.Left, prefix) && bytes.HasPrefix(ti.Right, prefix) { + onDiskSize += uint64(ti.OnDiskSize) + uncompressedSize += uint64(ti.UncompressedSize) + } + } + return onDiskSize, uncompressedSize +} + +// Ranges can be used to get rough key ranges to divide up iteration over the DB. The ranges here +// would consider the prefix, but would not necessarily start or end with the prefix. In fact, the +// first range would have nil as left key, and the last range would have nil as the right key. +func (db *DB) Ranges(prefix []byte, numRanges int) []*keyRange { + var splits []string + tables := db.Tables() + + // We just want table ranges here and not keys count. + for _, ti := range tables { + // We don't use ti.Left, because that has a tendency to store !badger keys. Skip over tables + // at upper levels. Only choose tables from the last level. + if ti.Level != db.opt.MaxLevels-1 { + continue + } + if bytes.HasPrefix(ti.Right, prefix) { + splits = append(splits, string(ti.Right)) + } + } + + // If the number of splits is low, look at the offsets inside the + // tables to generate more splits. + if len(splits) < 32 { + numTables := len(tables) + if numTables == 0 { + numTables = 1 + } + numPerTable := 32 / numTables + if numPerTable == 0 { + numPerTable = 1 + } + splits = db.lc.keySplits(numPerTable, prefix) + } + + // If the number of splits is still < 32, then look at the memtables. + if len(splits) < 32 { + maxPerSplit := 10000 + mtSplits := func(mt *memTable) { + if mt == nil { + return + } + count := 0 + iter := mt.sl.NewIterator() + for iter.SeekToFirst(); iter.Valid(); iter.Next() { + if count%maxPerSplit == 0 { + // Add a split every maxPerSplit keys. + if bytes.HasPrefix(iter.Key(), prefix) { + splits = append(splits, string(iter.Key())) + } + } + count += 1 + } + _ = iter.Close() + } + + db.lock.Lock() + defer db.lock.Unlock() + var memTables []*memTable + memTables = append(memTables, db.imm...) + for _, mt := range memTables { + mtSplits(mt) + } + mtSplits(db.mt) + } + + // We have our splits now. Let's convert them to ranges. + sort.Strings(splits) + var ranges []*keyRange + var start []byte + for _, key := range splits { + ranges = append(ranges, &keyRange{left: start, right: y.SafeCopy(nil, []byte(key))}) + start = y.SafeCopy(nil, []byte(key)) + } + ranges = append(ranges, &keyRange{left: start}) + + // Figure out the approximate table size this range has to deal with. + for _, t := range tables { + tr := keyRange{left: t.Left, right: t.Right} + for _, r := range ranges { + if len(r.left) == 0 || len(r.right) == 0 { + continue + } + if r.overlapsWith(tr) { + r.size += int64(t.UncompressedSize) + } + } + } + + var total int64 + for _, r := range ranges { + total += r.size + } + if total == 0 { + return ranges + } + // Figure out the average size, so we know how to bin the ranges together. + avg := total / int64(numRanges) + + var out []*keyRange + var i int + for i < len(ranges) { + r := ranges[i] + cur := &keyRange{left: r.left, size: r.size, right: r.right} + i++ + for ; i < len(ranges); i++ { + next := ranges[i] + if cur.size+next.size > avg { + break + } + cur.right = next.right + cur.size += next.size + } + out = append(out, cur) + } + return out +} + +// MaxBatchCount returns max possible entries in batch +func (db *DB) MaxBatchCount() int64 { + return db.opt.maxBatchCount +} + +// MaxBatchSize returns max possible batch size +func (db *DB) MaxBatchSize() int64 { + return db.opt.maxBatchSize +} + +func (db *DB) stopMemoryFlush() { + // Stop memtable flushes. + if db.closers.memtable != nil { + close(db.flushChan) + db.closers.memtable.SignalAndWait() + } +} + +func (db *DB) stopCompactions() { + // Stop compactions. + if db.closers.compactors != nil { + db.closers.compactors.SignalAndWait() + } +} + +func (db *DB) startCompactions() { + // Resume compactions. + if db.closers.compactors != nil { + db.closers.compactors = z.NewCloser(1) + db.lc.startCompact(db.closers.compactors) + } +} + +func (db *DB) startMemoryFlush() { + // Start memory fluhser. + if db.closers.memtable != nil { + db.flushChan = make(chan flushTask, db.opt.NumMemtables) + db.closers.memtable = z.NewCloser(1) + go func() { + _ = db.flushMemtable(db.closers.memtable) + }() + } +} + +// Flatten can be used to force compactions on the LSM tree so all the tables fall on the same +// level. This ensures that all the versions of keys are colocated and not split across multiple +// levels, which is necessary after a restore from backup. During Flatten, live compactions are +// stopped. Ideally, no writes are going on during Flatten. Otherwise, it would create competition +// between flattening the tree and new tables being created at level zero. +func (db *DB) Flatten(workers int) error { + + db.stopCompactions() + defer db.startCompactions() + + compactAway := func(cp compactionPriority) error { + db.opt.Infof("Attempting to compact with %+v\n", cp) + errCh := make(chan error, 1) + for i := 0; i < workers; i++ { + go func() { + errCh <- db.lc.doCompact(175, cp) + }() + } + var success int + var rerr error + for i := 0; i < workers; i++ { + err := <-errCh + if err != nil { + rerr = err + db.opt.Warningf("While running doCompact with %+v. Error: %v\n", cp, err) + } else { + success++ + } + } + if success == 0 { + return rerr + } + // We could do at least one successful compaction. So, we'll consider this a success. + db.opt.Infof("%d compactor(s) succeeded. One or more tables from level %d compacted.\n", + success, cp.level) + return nil + } + + hbytes := func(sz int64) string { + return humanize.IBytes(uint64(sz)) + } + + t := db.lc.levelTargets() + for { + db.opt.Infof("\n") + var levels []int + for i, l := range db.lc.levels { + sz := l.getTotalSize() + db.opt.Infof("Level: %d. %8s Size. %8s Max.\n", + i, hbytes(l.getTotalSize()), hbytes(t.targetSz[i])) + if sz > 0 { + levels = append(levels, i) + } + } + if len(levels) <= 1 { + prios := db.lc.pickCompactLevels() + if len(prios) == 0 || prios[0].score <= 1.0 { + db.opt.Infof("All tables consolidated into one level. Flattening done.\n") + return nil + } + if err := compactAway(prios[0]); err != nil { + return err + } + continue + } + // Create an artificial compaction priority, to ensure that we compact the level. + cp := compactionPriority{level: levels[0], score: 1.71} + if err := compactAway(cp); err != nil { + return err + } + } +} + +func (db *DB) blockWrite() error { + // Stop accepting new writes. + if !atomic.CompareAndSwapInt32(&db.blockWrites, 0, 1) { + return ErrBlockedWrites + } + + // Make all pending writes finish. The following will also close writeCh. + db.closers.writes.SignalAndWait() + db.opt.Infof("Writes flushed. Stopping compactions now...") + return nil +} + +func (db *DB) unblockWrite() { + db.closers.writes = z.NewCloser(1) + go db.doWrites(db.closers.writes) + + // Resume writes. + atomic.StoreInt32(&db.blockWrites, 0) +} + +func (db *DB) prepareToDrop() (func(), error) { + if db.opt.ReadOnly { + panic("Attempting to drop data in read-only mode.") + } + // In order prepare for drop, we need to block the incoming writes and + // write it to db. Then, flush all the pending flushtask. So that, we + // don't miss any entries. + if err := db.blockWrite(); err != nil { + return nil, err + } + reqs := make([]*request, 0, 10) + for { + select { + case r := <-db.writeCh: + reqs = append(reqs, r) + default: + if err := db.writeRequests(reqs); err != nil { + db.opt.Errorf("writeRequests: %v", err) + } + db.stopMemoryFlush() + return func() { + db.opt.Infof("Resuming writes") + db.startMemoryFlush() + db.unblockWrite() + }, nil + } + } +} + +// DropAll would drop all the data stored in Badger. It does this in the following way. +// - Stop accepting new writes. +// - Pause memtable flushes and compactions. +// - Pick all tables from all levels, create a changeset to delete all these +// tables and apply it to manifest. +// - Pick all log files from value log, and delete all of them. Restart value log files from zero. +// - Resume memtable flushes and compactions. +// +// NOTE: DropAll is resilient to concurrent writes, but not to reads. It is up to the user to not do +// any reads while DropAll is going on, otherwise they may result in panics. Ideally, both reads and +// writes are paused before running DropAll, and resumed after it is finished. +func (db *DB) DropAll() error { + f, err := db.dropAll() + if f != nil { + f() + } + return err +} + +func (db *DB) dropAll() (func(), error) { + db.opt.Infof("DropAll called. Blocking writes...") + f, err := db.prepareToDrop() + if err != nil { + return f, err + } + // prepareToDrop will stop all the incomming write and flushes any pending flush tasks. + // Before we drop, we'll stop the compaction because anyways all the datas are going to + // be deleted. + db.stopCompactions() + resume := func() { + db.startCompactions() + f() + } + // Block all foreign interactions with memory tables. + db.lock.Lock() + defer db.lock.Unlock() + + // Remove inmemory tables. Calling DecrRef for safety. Not sure if they're absolutely needed. + db.mt.DecrRef() + for _, mt := range db.imm { + mt.DecrRef() + } + db.imm = db.imm[:0] + db.mt, err = db.newMemTable() // Set it up for future writes. + if err != nil { + return resume, y.Wrapf(err, "cannot open new memtable") + } + + num, err := db.lc.dropTree() + if err != nil { + return resume, err + } + db.opt.Infof("Deleted %d SSTables. Now deleting value logs...\n", num) + + num, err = db.vlog.dropAll() + if err != nil { + return resume, err + } + db.lc.nextFileID = 1 + db.opt.Infof("Deleted %d value log files. DropAll done.\n", num) + db.blockCache.Clear() + db.indexCache.Clear() + db.threshold.Clear(db.opt) + return resume, nil +} + +// DropPrefix would drop all the keys with the provided prefix. It does this in the following way: +// - Stop accepting new writes. +// - Stop memtable flushes before acquiring lock. Because we're acquring lock here +// and memtable flush stalls for lock, which leads to deadlock +// - Flush out all memtables, skipping over keys with the given prefix, Kp. +// - Write out the value log header to memtables when flushing, so we don't accidentally bring Kp +// back after a restart. +// - Stop compaction. +// - Compact L0->L1, skipping over Kp. +// - Compact rest of the levels, Li->Li, picking tables which have Kp. +// - Resume memtable flushes, compactions and writes. +func (db *DB) DropPrefix(prefixes ...[]byte) error { + if len(prefixes) == 0 { + return nil + } + db.opt.Infof("DropPrefix called for %s", prefixes) + f, err := db.prepareToDrop() + if err != nil { + return err + } + defer f() + + var filtered [][]byte + if filtered, err = db.filterPrefixesToDrop(prefixes); err != nil { + return err + } + // If there is no prefix for which the data already exist, do not do anything. + if len(filtered) == 0 { + db.opt.Infof("No prefixes to drop") + return nil + } + // Block all foreign interactions with memory tables. + db.lock.Lock() + defer db.lock.Unlock() + + db.imm = append(db.imm, db.mt) + for _, memtable := range db.imm { + if memtable.sl.Empty() { + memtable.DecrRef() + continue + } + task := flushTask{ + mt: memtable, + // Ensure that the head of value log gets persisted to disk. + dropPrefixes: filtered, + } + db.opt.Debugf("Flushing memtable") + if err := db.handleFlushTask(task); err != nil { + db.opt.Errorf("While trying to flush memtable: %v", err) + return err + } + memtable.DecrRef() + } + db.stopCompactions() + defer db.startCompactions() + db.imm = db.imm[:0] + db.mt, err = db.newMemTable() + if err != nil { + return y.Wrapf(err, "cannot create new mem table") + } + + // Drop prefixes from the levels. + if err := db.lc.dropPrefixes(filtered); err != nil { + return err + } + db.opt.Infof("DropPrefix done") + return nil +} + +func (db *DB) filterPrefixesToDrop(prefixes [][]byte) ([][]byte, error) { + var filtered [][]byte + for _, prefix := range prefixes { + err := db.View(func(txn *Txn) error { + iopts := DefaultIteratorOptions + iopts.Prefix = prefix + iopts.PrefetchValues = false + itr := txn.NewIterator(iopts) + defer itr.Close() + itr.Rewind() + if itr.ValidForPrefix(prefix) { + filtered = append(filtered, prefix) + } + return nil + }) + if err != nil { + return filtered, err + } + } + return filtered, nil +} + +// Checks if the key is banned. Returns the respective error if the key belongs to any of the banned +// namepspaces. Else it returns nil. +func (db *DB) isBanned(key []byte) error { + if db.opt.NamespaceOffset < 0 { + return nil + } + if len(key) <= db.opt.NamespaceOffset+8 { + return nil + } + if db.bannedNamespaces.has(y.BytesToU64(key[db.opt.NamespaceOffset:])) { + return ErrBannedKey + } + return nil +} + +// BanNamespace bans a namespace. Read/write to keys belonging to any of such namespace is denied. +func (db *DB) BanNamespace(ns uint64) error { + if db.opt.NamespaceOffset < 0 { + return ErrNamespaceMode + } + db.opt.Infof("Banning namespace: %d", ns) + // First set the banned namespaces in DB and then update the in-memory structure. + key := y.KeyWithTs(append(bannedNsKey, y.U64ToBytes(ns)...), 1) + entry := []*Entry{{ + Key: key, + Value: nil, + }} + req, err := db.sendToWriteCh(entry) + if err != nil { + return err + } + if err := req.Wait(); err != nil { + return err + } + db.bannedNamespaces.add(ns) + return nil +} + +// BannedNamespaces returns the list of prefixes banned for DB. +func (db *DB) BannedNamespaces() []uint64 { + return db.bannedNamespaces.all() +} + +// KVList contains a list of key-value pairs. +type KVList = pb.KVList + +// Subscribe can be used to watch key changes for the given key prefixes and the ignore string. +// At least one prefix should be passed, or an error will be returned. +// You can use an empty prefix to monitor all changes to the DB. +// Ignore string is the byte ranges for which prefix matching will be ignored. +// For example: ignore = "2-3", and prefix = "abc" will match for keys "abxxc", "abdfc" etc. +// This function blocks until the given context is done or an error occurs. +// The given function will be called with a new KVList containing the modified keys and the +// corresponding values. +func (db *DB) Subscribe(ctx context.Context, cb func(kv *KVList) error, matches []pb.Match) error { + if cb == nil { + return ErrNilCallback + } + + c := z.NewCloser(1) + s := db.pub.newSubscriber(c, matches) + slurp := func(batch *pb.KVList) error { + for { + select { + case kvs := <-s.sendCh: + batch.Kv = append(batch.Kv, kvs.Kv...) + default: + if len(batch.GetKv()) > 0 { + return cb(batch) + } + return nil + } + } + } + + drain := func() { + for { + select { + case <-s.sendCh: + default: + return + } + } + } + for { + select { + case <-c.HasBeenClosed(): + // No need to delete here. Closer will be called only while + // closing DB. Subscriber will be deleted by cleanSubscribers. + err := slurp(new(pb.KVList)) + // Drain if any pending updates. + c.Done() + return err + case <-ctx.Done(): + c.Done() + atomic.StoreUint64(s.active, 0) + drain() + db.pub.deleteSubscriber(s.id) + // Delete the subscriber to avoid further updates. + return ctx.Err() + case batch := <-s.sendCh: + err := slurp(batch) + if err != nil { + c.Done() + atomic.StoreUint64(s.active, 0) + drain() + // Delete the subscriber if there is an error by the callback. + db.pub.deleteSubscriber(s.id) + return err + } + } + } +} + +// shouldEncrypt returns bool, which tells whether to encrypt or not. +func (db *DB) shouldEncrypt() bool { + return len(db.opt.EncryptionKey) > 0 +} + +func (db *DB) syncDir(dir string) error { + if db.opt.InMemory { + return nil + } + return syncDir(dir) +} + +func createDirs(opt Options) error { + for _, path := range []string{opt.Dir, opt.ValueDir} { + dirExists, err := exists(path) + if err != nil { + return y.Wrapf(err, "Invalid Dir: %q", path) + } + if !dirExists { + if opt.ReadOnly { + return errors.Errorf("Cannot find directory %q for read-only open", path) + } + // Try to create the directory + err = os.MkdirAll(path, 0700) + if err != nil { + return y.Wrapf(err, "Error Creating Dir: %q", path) + } + } + } + return nil +} + +// Stream the contents of this DB to a new DB with options outOptions that will be +// created in outDir. +func (db *DB) StreamDB(outOptions Options) error { + outDir := outOptions.Dir + + // Open output DB. + outDB, err := OpenManaged(outOptions) + if err != nil { + return y.Wrapf(err, "cannot open out DB at %s", outDir) + } + defer outDB.Close() + writer := outDB.NewStreamWriter() + if err := writer.Prepare(); err != nil { + y.Wrapf(err, "cannot create stream writer in out DB at %s", outDir) + } + + // Stream contents of DB to the output DB. + stream := db.NewStreamAt(math.MaxUint64) + stream.LogPrefix = fmt.Sprintf("Streaming DB to new DB at %s", outDir) + + stream.Send = func(buf *z.Buffer) error { + return writer.Write(buf) + } + if err := stream.Orchestrate(context.Background()); err != nil { + return y.Wrapf(err, "cannot stream DB to out DB at %s", outDir) + } + if err := writer.Flush(); err != nil { + return y.Wrapf(err, "cannot flush writer") + } + return nil +} + +// Opts returns a copy of the DB options. +func (db *DB) Opts() Options { + return db.opt +} + +type CacheType int + +const ( + BlockCache CacheType = iota + IndexCache +) + +// CacheMaxCost updates the max cost of the given cache (either block or index cache). +// The call will have an effect only if the DB was created with the cache. Otherwise it is +// a no-op. If you pass a negative value, the function will return the current value +// without updating it. +func (db *DB) CacheMaxCost(cache CacheType, maxCost int64) (int64, error) { + if db == nil { + return 0, nil + } + + if maxCost < 0 { + switch cache { + case BlockCache: + return db.blockCache.MaxCost(), nil + case IndexCache: + return db.indexCache.MaxCost(), nil + default: + return 0, errors.Errorf("invalid cache type") + } + } + + switch cache { + case BlockCache: + db.blockCache.UpdateMaxCost(maxCost) + return maxCost, nil + case IndexCache: + db.indexCache.UpdateMaxCost(maxCost) + return maxCost, nil + default: + return 0, errors.Errorf("invalid cache type") + } +} + +func (db *DB) LevelsToString() string { + levels := db.Levels() + h := func(sz int64) string { + return humanize.IBytes(uint64(sz)) + } + base := func(b bool) string { + if b { + return "B" + } + return " " + } + + var b strings.Builder + b.WriteRune('\n') + for _, li := range levels { + b.WriteString(fmt.Sprintf( + "Level %d [%s]: NumTables: %02d. Size: %s of %s. Score: %.2f->%.2f"+ + " StaleData: %s Target FileSize: %s\n", + li.Level, base(li.IsBaseLevel), li.NumTables, + h(li.Size), h(li.TargetSize), li.Score, li.Adjusted, h(li.StaleDatSize), + h(li.TargetFileSize))) + } + b.WriteString("Level Done\n") + return b.String() +} diff --git a/vendor/github.com/dgraph-io/badger/v3/dir_plan9.go b/vendor/github.com/dgraph-io/badger/v3/dir_plan9.go new file mode 100644 index 0000000000..76e3fa2111 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/dir_plan9.go @@ -0,0 +1,150 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/dgraph-io/badger/v3/y" +) + +// directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part +// of the locking mechanism, it's just advisory. +type directoryLockGuard struct { + // File handle on the directory, which we've locked. + f *os.File + // The absolute path to our pid file. + path string +} + +// acquireDirectoryLock gets a lock on the directory. +// It will also write our pid to dirPath/pidFileName for convenience. +// readOnly is not supported on Plan 9. +func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) ( + *directoryLockGuard, error) { + if readOnly { + return nil, ErrPlan9NotSupported + } + + // Convert to absolute path so that Release still works even if we do an unbalanced + // chdir in the meantime. + absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) + if err != nil { + return nil, y.Wrap(err, "cannot get absolute path for pid lock file") + } + + // If the file was unpacked or created by some other program, it might not + // have the ModeExclusive bit set. Set it before we call OpenFile, so that we + // can be confident that a successful OpenFile implies exclusive use. + // + // OpenFile fails if the file ModeExclusive bit set *and* the file is already open. + // So, if the file is closed when the DB crashed, we're fine. When the process + // that was managing the DB crashes, the OS will close the file for us. + // + // This bit of code is copied from Go's lockedfile internal package: + // https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L58 + if fi, err := os.Stat(absPidFilePath); err == nil { + if fi.Mode()&os.ModeExclusive == 0 { + if err := os.Chmod(absPidFilePath, fi.Mode()|os.ModeExclusive); err != nil { + return nil, y.Wrapf(err, "could not set exclusive mode bit") + } + } + } else if !os.IsNotExist(err) { + return nil, err + } + f, err := os.OpenFile(absPidFilePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666|os.ModeExclusive) + if err != nil { + if isLocked(err) { + return nil, y.Wrapf(err, + "Cannot open pid lock file %q. Another process is using this Badger database", + absPidFilePath) + } + return nil, y.Wrapf(err, "Cannot open pid lock file %q", absPidFilePath) + } + + if _, err = fmt.Fprintf(f, "%d\n", os.Getpid()); err != nil { + f.Close() + return nil, y.Wrapf(err, "could not write pid") + } + return &directoryLockGuard{f, absPidFilePath}, nil +} + +// Release deletes the pid file and releases our lock on the directory. +func (guard *directoryLockGuard) release() error { + // It's important that we remove the pid file first. + err := os.Remove(guard.path) + + if closeErr := guard.f.Close(); err == nil { + err = closeErr + } + guard.path = "" + guard.f = nil + + return err +} + +// openDir opens a directory for syncing. +func openDir(path string) (*os.File, error) { return os.Open(path) } + +// When you create or delete a file, you have to ensure the directory entry for the file is synced +// in order to guarantee the file is visible (if the system crashes). (See the man page for fsync, +// or see https://github.com/coreos/etcd/issues/6368 for an example.) +func syncDir(dir string) error { + f, err := openDir(dir) + if err != nil { + return y.Wrapf(err, "While opening directory: %s.", dir) + } + + err = f.Sync() + closeErr := f.Close() + if err != nil { + return y.Wrapf(err, "While syncing directory: %s.", dir) + } + return y.Wrapf(closeErr, "While closing directory: %s.", dir) +} + +// Opening an exclusive-use file returns an error. +// The expected error strings are: +// +// - "open/create -- file is locked" (cwfs, kfs) +// - "exclusive lock" (fossil) +// - "exclusive use file already open" (ramfs) +// +// See https://github.com/golang/go/blob/go1.15rc1/src/cmd/go/internal/lockedfile/lockedfile_plan9.go#L16 +var lockedErrStrings = [...]string{ + "file is locked", + "exclusive lock", + "exclusive use file already open", +} + +// Even though plan9 doesn't support the Lock/RLock/Unlock functions to +// manipulate already-open files, IsLocked is still meaningful: os.OpenFile +// itself may return errors that indicate that a file with the ModeExclusive bit +// set is already open. +func isLocked(err error) bool { + s := err.Error() + + for _, frag := range lockedErrStrings { + if strings.Contains(s, frag) { + return true + } + } + return false +} diff --git a/vendor/github.com/dgraph-io/badger/v3/dir_unix.go b/vendor/github.com/dgraph-io/badger/v3/dir_unix.go new file mode 100644 index 0000000000..48addc976a --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/dir_unix.go @@ -0,0 +1,118 @@ +// +build !windows,!plan9 + +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/dgraph-io/badger/v3/y" + "golang.org/x/sys/unix" +) + +// directoryLockGuard holds a lock on a directory and a pid file inside. The pid file isn't part +// of the locking mechanism, it's just advisory. +type directoryLockGuard struct { + // File handle on the directory, which we've flocked. + f *os.File + // The absolute path to our pid file. + path string + // Was this a shared lock for a read-only database? + readOnly bool +} + +// acquireDirectoryLock gets a lock on the directory (using flock). If +// this is not read-only, it will also write our pid to +// dirPath/pidFileName for convenience. +func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) ( + *directoryLockGuard, error) { + // Convert to absolute path so that Release still works even if we do an unbalanced + // chdir in the meantime. + absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) + if err != nil { + return nil, y.Wrapf(err, "cannot get absolute path for pid lock file") + } + f, err := os.Open(dirPath) + if err != nil { + return nil, y.Wrapf(err, "cannot open directory %q", dirPath) + } + opts := unix.LOCK_EX | unix.LOCK_NB + if readOnly { + opts = unix.LOCK_SH | unix.LOCK_NB + } + + err = unix.Flock(int(f.Fd()), opts) + if err != nil { + f.Close() + return nil, y.Wrapf(err, + "Cannot acquire directory lock on %q. Another process is using this Badger database.", + dirPath) + } + + if !readOnly { + // Yes, we happily overwrite a pre-existing pid file. We're the + // only read-write badger process using this directory. + err = ioutil.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666) + if err != nil { + f.Close() + return nil, y.Wrapf(err, + "Cannot write pid file %q", absPidFilePath) + } + } + return &directoryLockGuard{f, absPidFilePath, readOnly}, nil +} + +// Release deletes the pid file and releases our lock on the directory. +func (guard *directoryLockGuard) release() error { + var err error + if !guard.readOnly { + // It's important that we remove the pid file first. + err = os.Remove(guard.path) + } + + if closeErr := guard.f.Close(); err == nil { + err = closeErr + } + guard.path = "" + guard.f = nil + + return err +} + +// openDir opens a directory for syncing. +func openDir(path string) (*os.File, error) { return os.Open(path) } + +// When you create or delete a file, you have to ensure the directory entry for the file is synced +// in order to guarantee the file is visible (if the system crashes). (See the man page for fsync, +// or see https://github.com/coreos/etcd/issues/6368 for an example.) +func syncDir(dir string) error { + f, err := openDir(dir) + if err != nil { + return y.Wrapf(err, "While opening directory: %s.", dir) + } + + err = f.Sync() + closeErr := f.Close() + if err != nil { + return y.Wrapf(err, "While syncing directory: %s.", dir) + } + return y.Wrapf(closeErr, "While closing directory: %s.", dir) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/dir_windows.go b/vendor/github.com/dgraph-io/badger/v3/dir_windows.go new file mode 100644 index 0000000000..43b00e37c7 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/dir_windows.go @@ -0,0 +1,110 @@ +// +build windows + +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +// OpenDir opens a directory in windows with write access for syncing. +import ( + "os" + "path/filepath" + "syscall" + + "github.com/dgraph-io/badger/v3/y" +) + +// FILE_ATTRIBUTE_TEMPORARY - A file that is being used for temporary storage. +// FILE_FLAG_DELETE_ON_CLOSE - The file is to be deleted immediately after all of its handles are +// closed, which includes the specified handle and any other open or duplicated handles. +// See: https://docs.microsoft.com/en-us/windows/desktop/FileIO/file-attribute-constants +// NOTE: Added here to avoid importing golang.org/x/sys/windows +const ( + FILE_ATTRIBUTE_TEMPORARY = 0x00000100 + FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 +) + +func openDir(path string) (*os.File, error) { + fd, err := openDirWin(path) + if err != nil { + return nil, err + } + return os.NewFile(uintptr(fd), path), nil +} + +func openDirWin(path string) (fd syscall.Handle, err error) { + if len(path) == 0 { + return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND + } + pathp, err := syscall.UTF16PtrFromString(path) + if err != nil { + return syscall.InvalidHandle, err + } + access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE) + sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE) + createmode := uint32(syscall.OPEN_EXISTING) + fl := uint32(syscall.FILE_FLAG_BACKUP_SEMANTICS) + return syscall.CreateFile(pathp, access, sharemode, nil, createmode, fl, 0) +} + +// DirectoryLockGuard holds a lock on the directory. +type directoryLockGuard struct { + h syscall.Handle + path string +} + +// AcquireDirectoryLock acquires exclusive access to a directory. +func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) { + if readOnly { + return nil, ErrWindowsNotSupported + } + + // Convert to absolute path so that Release still works even if we do an unbalanced + // chdir in the meantime. + absLockFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) + if err != nil { + return nil, y.Wrap(err, "Cannot get absolute path for pid lock file") + } + + // This call creates a file handler in memory that only one process can use at a time. When + // that process ends, the file is deleted by the system. + // FILE_ATTRIBUTE_TEMPORARY is used to tell Windows to try to create the handle in memory. + // FILE_FLAG_DELETE_ON_CLOSE is not specified in syscall_windows.go but tells Windows to delete + // the file when all processes holding the handler are closed. + // XXX: this works but it's a bit klunky. i'd prefer to use LockFileEx but it needs unsafe pkg. + h, err := syscall.CreateFile( + syscall.StringToUTF16Ptr(absLockFilePath), 0, 0, nil, + syscall.OPEN_ALWAYS, + uint32(FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE), + 0) + if err != nil { + return nil, y.Wrapf(err, + "Cannot create lock file %q. Another process is using this Badger database", + absLockFilePath) + } + + return &directoryLockGuard{h: h, path: absLockFilePath}, nil +} + +// Release removes the directory lock. +func (g *directoryLockGuard) release() error { + g.path = "" + return syscall.CloseHandle(g.h) +} + +// Windows doesn't support syncing directories to the file system. See +// https://github.com/dgraph-io/badger/issues/699#issuecomment-504133587 for more details. +func syncDir(dir string) error { return nil } diff --git a/vendor/github.com/dgraph-io/badger/v3/discard.go b/vendor/github.com/dgraph-io/badger/v3/discard.go new file mode 100644 index 0000000000..050d5caa2e --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/discard.go @@ -0,0 +1,168 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "encoding/binary" + "os" + "path/filepath" + "sort" + "sync" + + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" +) + +// discardStats keeps track of the amount of data that could be discarded for +// a given logfile. +type discardStats struct { + sync.Mutex + + *z.MmapFile + opt Options + nextEmptySlot int +} + +const discardFname string = "DISCARD" + +func InitDiscardStats(opt Options) (*discardStats, error) { + fname := filepath.Join(opt.ValueDir, discardFname) + + // 1GB file can store 67M discard entries. Each entry is 16 bytes. + mf, err := z.OpenMmapFile(fname, os.O_CREATE|os.O_RDWR, 1<<20) + lf := &discardStats{ + MmapFile: mf, + opt: opt, + } + if err == z.NewFile { + // We don't need to zero out the entire 1GB. + lf.zeroOut() + + } else if err != nil { + return nil, y.Wrapf(err, "while opening file: %s\n", discardFname) + } + + for slot := 0; slot < lf.maxSlot(); slot++ { + if lf.get(16*slot) == 0 { + lf.nextEmptySlot = slot + break + } + } + sort.Sort(lf) + opt.Infof("Discard stats nextEmptySlot: %d\n", lf.nextEmptySlot) + return lf, nil +} + +func (lf *discardStats) Len() int { + return lf.nextEmptySlot +} +func (lf *discardStats) Less(i, j int) bool { + return lf.get(16*i) < lf.get(16*j) +} +func (lf *discardStats) Swap(i, j int) { + left := lf.Data[16*i : 16*i+16] + right := lf.Data[16*j : 16*j+16] + var tmp [16]byte + copy(tmp[:], left) + copy(left, right) + copy(right, tmp[:]) +} + +// offset is not slot. +func (lf *discardStats) get(offset int) uint64 { + return binary.BigEndian.Uint64(lf.Data[offset : offset+8]) +} +func (lf *discardStats) set(offset int, val uint64) { + binary.BigEndian.PutUint64(lf.Data[offset:offset+8], val) +} + +// zeroOut would zero out the next slot. +func (lf *discardStats) zeroOut() { + lf.set(lf.nextEmptySlot*16, 0) + lf.set(lf.nextEmptySlot*16+8, 0) +} + +func (lf *discardStats) maxSlot() int { + return len(lf.Data) / 16 +} + +// Update would update the discard stats for the given file id. If discard is +// 0, it would return the current value of discard for the file. If discard is +// < 0, it would set the current value of discard to zero for the file. +func (lf *discardStats) Update(fidu uint32, discard int64) int64 { + fid := uint64(fidu) + lf.Lock() + defer lf.Unlock() + + idx := sort.Search(lf.nextEmptySlot, func(slot int) bool { + return lf.get(slot*16) >= fid + }) + if idx < lf.nextEmptySlot && lf.get(idx*16) == fid { + off := idx*16 + 8 + curDisc := lf.get(off) + if discard == 0 { + return int64(curDisc) + } + if discard < 0 { + lf.set(off, 0) + return 0 + } + lf.set(off, curDisc+uint64(discard)) + return int64(curDisc + uint64(discard)) + } + if discard <= 0 { + // No need to add a new entry. + return 0 + } + + // Could not find the fid. Add the entry. + idx = lf.nextEmptySlot + lf.set(idx*16, uint64(fid)) + lf.set(idx*16+8, uint64(discard)) + + // Move to next slot. + lf.nextEmptySlot++ + for lf.nextEmptySlot >= lf.maxSlot() { + y.Check(lf.Truncate(2 * int64(len(lf.Data)))) + } + lf.zeroOut() + + sort.Sort(lf) + return int64(discard) +} + +func (lf *discardStats) Iterate(f func(fid, stats uint64)) { + for slot := 0; slot < lf.nextEmptySlot; slot++ { + idx := 16 * slot + f(lf.get(idx), lf.get(idx+8)) + } +} + +// MaxDiscard returns the file id with maximum discard bytes. +func (lf *discardStats) MaxDiscard() (uint32, int64) { + lf.Lock() + defer lf.Unlock() + + var maxFid, maxVal uint64 + lf.Iterate(func(fid, val uint64) { + if maxVal < val { + maxVal = val + maxFid = fid + } + }) + return uint32(maxFid), int64(maxVal) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/doc.go b/vendor/github.com/dgraph-io/badger/v3/doc.go new file mode 100644 index 0000000000..83dc9a28ac --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/doc.go @@ -0,0 +1,28 @@ +/* +Package badger implements an embeddable, simple and fast key-value database, +written in pure Go. It is designed to be highly performant for both reads and +writes simultaneously. Badger uses Multi-Version Concurrency Control (MVCC), and +supports transactions. It runs transactions concurrently, with serializable +snapshot isolation guarantees. + +Badger uses an LSM tree along with a value log to separate keys from values, +hence reducing both write amplification and the size of the LSM tree. This +allows LSM tree to be served entirely from RAM, while the values are served +from SSD. + + +Usage + +Badger has the following main types: DB, Txn, Item and Iterator. DB contains +keys that are associated with values. It must be opened with the appropriate +options before it can be accessed. + +All operations happen inside a Txn. Txn represents a transaction, which can +be read-only or read-write. Read-only transactions can read values for a +given key (which are returned inside an Item), or iterate over a set of +key-value pairs using an Iterator (which are returned as Item type values as +well). Read-write transactions can also update and delete keys from the DB. + +See the examples for more usage details. +*/ +package badger diff --git a/vendor/github.com/dgraph-io/badger/v3/errors.go b/vendor/github.com/dgraph-io/badger/v3/errors.go new file mode 100644 index 0000000000..f5df6d5118 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/errors.go @@ -0,0 +1,126 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "math" + + "github.com/pkg/errors" +) + +const ( + // ValueThresholdLimit is the maximum permissible value of opt.ValueThreshold. + ValueThresholdLimit = math.MaxUint16 - 16 + 1 +) + +var ( + // ErrValueLogSize is returned when opt.ValueLogFileSize option is not within the valid + // range. + ErrValueLogSize = errors.New("Invalid ValueLogFileSize, must be in range [1MB, 2GB)") + + // ErrKeyNotFound is returned when key isn't found on a txn.Get. + ErrKeyNotFound = errors.New("Key not found") + + // ErrTxnTooBig is returned if too many writes are fit into a single transaction. + ErrTxnTooBig = errors.New("Txn is too big to fit into one request") + + // ErrConflict is returned when a transaction conflicts with another transaction. This can + // happen if the read rows had been updated concurrently by another transaction. + ErrConflict = errors.New("Transaction Conflict. Please retry") + + // ErrReadOnlyTxn is returned if an update function is called on a read-only transaction. + ErrReadOnlyTxn = errors.New("No sets or deletes are allowed in a read-only transaction") + + // ErrDiscardedTxn is returned if a previously discarded transaction is re-used. + ErrDiscardedTxn = errors.New("This transaction has been discarded. Create a new one") + + // ErrEmptyKey is returned if an empty key is passed on an update function. + ErrEmptyKey = errors.New("Key cannot be empty") + + // ErrInvalidKey is returned if the key has a special !badger! prefix, + // reserved for internal usage. + ErrInvalidKey = errors.New("Key is using a reserved !badger! prefix") + + // ErrBannedKey is returned if the read/write key belongs to any banned namespace. + ErrBannedKey = errors.New("Key is using the banned prefix") + + // ErrThresholdZero is returned if threshold is set to zero, and value log GC is called. + // In such a case, GC can't be run. + ErrThresholdZero = errors.New( + "Value log GC can't run because threshold is set to zero") + + // ErrNoRewrite is returned if a call for value log GC doesn't result in a log file rewrite. + ErrNoRewrite = errors.New( + "Value log GC attempt didn't result in any cleanup") + + // ErrRejected is returned if a value log GC is called either while another GC is running, or + // after DB::Close has been called. + ErrRejected = errors.New("Value log GC request rejected") + + // ErrInvalidRequest is returned if the user request is invalid. + ErrInvalidRequest = errors.New("Invalid request") + + // ErrManagedTxn is returned if the user tries to use an API which isn't + // allowed due to external management of transactions, when using ManagedDB. + ErrManagedTxn = errors.New( + "Invalid API request. Not allowed to perform this action using ManagedDB") + + // ErrNamespaceMode is returned if the user tries to use an API which is allowed only when + // NamespaceOffset is non-negative. + ErrNamespaceMode = errors.New( + "Invalid API request. Not allowed to perform this action when NamespaceMode is not set.") + + // ErrInvalidDump if a data dump made previously cannot be loaded into the database. + ErrInvalidDump = errors.New("Data dump cannot be read") + + // ErrZeroBandwidth is returned if the user passes in zero bandwidth for sequence. + ErrZeroBandwidth = errors.New("Bandwidth must be greater than zero") + + // ErrWindowsNotSupported is returned when opt.ReadOnly is used on Windows + ErrWindowsNotSupported = errors.New("Read-only mode is not supported on Windows") + + // ErrPlan9NotSupported is returned when opt.ReadOnly is used on Plan 9 + ErrPlan9NotSupported = errors.New("Read-only mode is not supported on Plan 9") + + // ErrTruncateNeeded is returned when the value log gets corrupt, and requires truncation of + // corrupt data to allow Badger to run properly. + ErrTruncateNeeded = errors.New( + "Log truncate required to run DB. This might result in data loss") + + // ErrBlockedWrites is returned if the user called DropAll. During the process of dropping all + // data from Badger, we stop accepting new writes, by returning this error. + ErrBlockedWrites = errors.New("Writes are blocked, possibly due to DropAll or Close") + + // ErrNilCallback is returned when subscriber's callback is nil. + ErrNilCallback = errors.New("Callback cannot be nil") + + // ErrEncryptionKeyMismatch is returned when the storage key is not + // matched with the key previously given. + ErrEncryptionKeyMismatch = errors.New("Encryption key mismatch") + + // ErrInvalidDataKeyID is returned if the datakey id is invalid. + ErrInvalidDataKeyID = errors.New("Invalid datakey id") + + // ErrInvalidEncryptionKey is returned if length of encryption keys is invalid. + ErrInvalidEncryptionKey = errors.New("Encryption key's length should be" + + "either 16, 24, or 32 bytes") + // ErrGCInMemoryMode is returned when db.RunValueLogGC is called in in-memory mode. + ErrGCInMemoryMode = errors.New("Cannot run value log GC when DB is opened in InMemory mode") + + // ErrDBClosed is returned when a get operation is performed after closing the DB. + ErrDBClosed = errors.New("DB Closed") +) diff --git a/vendor/github.com/dgraph-io/badger/v3/fb/BlockOffset.go b/vendor/github.com/dgraph-io/badger/v3/fb/BlockOffset.go new file mode 100644 index 0000000000..6ef437e250 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/fb/BlockOffset.go @@ -0,0 +1,104 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package fb + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type BlockOffset struct { + _tab flatbuffers.Table +} + +func GetRootAsBlockOffset(buf []byte, offset flatbuffers.UOffsetT) *BlockOffset { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &BlockOffset{} + x.Init(buf, n+offset) + return x +} + +func (rcv *BlockOffset) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *BlockOffset) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *BlockOffset) Key(j int) byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.GetByte(a + flatbuffers.UOffsetT(j*1)) + } + return 0 +} + +func (rcv *BlockOffset) KeyLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *BlockOffset) KeyBytes() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *BlockOffset) MutateKey(j int, n byte) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.MutateByte(a+flatbuffers.UOffsetT(j*1), n) + } + return false +} + +func (rcv *BlockOffset) Offset() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *BlockOffset) MutateOffset(n uint32) bool { + return rcv._tab.MutateUint32Slot(6, n) +} + +func (rcv *BlockOffset) Len() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *BlockOffset) MutateLen(n uint32) bool { + return rcv._tab.MutateUint32Slot(8, n) +} + +func BlockOffsetStart(builder *flatbuffers.Builder) { + builder.StartObject(3) +} +func BlockOffsetAddKey(builder *flatbuffers.Builder, key flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(key), 0) +} +func BlockOffsetStartKeyVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(1, numElems, 1) +} +func BlockOffsetAddOffset(builder *flatbuffers.Builder, offset uint32) { + builder.PrependUint32Slot(1, offset, 0) +} +func BlockOffsetAddLen(builder *flatbuffers.Builder, len uint32) { + builder.PrependUint32Slot(2, len, 0) +} +func BlockOffsetEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/vendor/github.com/dgraph-io/badger/v3/fb/TableIndex.go b/vendor/github.com/dgraph-io/badger/v3/fb/TableIndex.go new file mode 100644 index 0000000000..7b4074b57a --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/fb/TableIndex.go @@ -0,0 +1,175 @@ +// Code generated by the FlatBuffers compiler. DO NOT EDIT. + +package fb + +import ( + flatbuffers "github.com/google/flatbuffers/go" +) + +type TableIndex struct { + _tab flatbuffers.Table +} + +func GetRootAsTableIndex(buf []byte, offset flatbuffers.UOffsetT) *TableIndex { + n := flatbuffers.GetUOffsetT(buf[offset:]) + x := &TableIndex{} + x.Init(buf, n+offset) + return x +} + +func (rcv *TableIndex) Init(buf []byte, i flatbuffers.UOffsetT) { + rcv._tab.Bytes = buf + rcv._tab.Pos = i +} + +func (rcv *TableIndex) Table() flatbuffers.Table { + return rcv._tab +} + +func (rcv *TableIndex) Offsets(obj *BlockOffset, j int) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + x := rcv._tab.Vector(o) + x += flatbuffers.UOffsetT(j) * 4 + x = rcv._tab.Indirect(x) + obj.Init(rcv._tab.Bytes, x) + return true + } + return false +} + +func (rcv *TableIndex) OffsetsLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(4)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *TableIndex) BloomFilter(j int) byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.GetByte(a + flatbuffers.UOffsetT(j*1)) + } + return 0 +} + +func (rcv *TableIndex) BloomFilterLength() int { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.VectorLen(o) + } + return 0 +} + +func (rcv *TableIndex) BloomFilterBytes() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *TableIndex) MutateBloomFilter(j int, n byte) bool { + o := flatbuffers.UOffsetT(rcv._tab.Offset(6)) + if o != 0 { + a := rcv._tab.Vector(o) + return rcv._tab.MutateByte(a+flatbuffers.UOffsetT(j*1), n) + } + return false +} + +func (rcv *TableIndex) MaxVersion() uint64 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(8)) + if o != 0 { + return rcv._tab.GetUint64(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *TableIndex) MutateMaxVersion(n uint64) bool { + return rcv._tab.MutateUint64Slot(8, n) +} + +func (rcv *TableIndex) KeyCount() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(10)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *TableIndex) MutateKeyCount(n uint32) bool { + return rcv._tab.MutateUint32Slot(10, n) +} + +func (rcv *TableIndex) UncompressedSize() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(12)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *TableIndex) MutateUncompressedSize(n uint32) bool { + return rcv._tab.MutateUint32Slot(12, n) +} + +func (rcv *TableIndex) OnDiskSize() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(14)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *TableIndex) MutateOnDiskSize(n uint32) bool { + return rcv._tab.MutateUint32Slot(14, n) +} + +func (rcv *TableIndex) StaleDataSize() uint32 { + o := flatbuffers.UOffsetT(rcv._tab.Offset(16)) + if o != 0 { + return rcv._tab.GetUint32(o + rcv._tab.Pos) + } + return 0 +} + +func (rcv *TableIndex) MutateStaleDataSize(n uint32) bool { + return rcv._tab.MutateUint32Slot(16, n) +} + +func TableIndexStart(builder *flatbuffers.Builder) { + builder.StartObject(7) +} +func TableIndexAddOffsets(builder *flatbuffers.Builder, offsets flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(0, flatbuffers.UOffsetT(offsets), 0) +} +func TableIndexStartOffsetsVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(4, numElems, 4) +} +func TableIndexAddBloomFilter(builder *flatbuffers.Builder, bloomFilter flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(1, flatbuffers.UOffsetT(bloomFilter), 0) +} +func TableIndexStartBloomFilterVector(builder *flatbuffers.Builder, numElems int) flatbuffers.UOffsetT { + return builder.StartVector(1, numElems, 1) +} +func TableIndexAddMaxVersion(builder *flatbuffers.Builder, maxVersion uint64) { + builder.PrependUint64Slot(2, maxVersion, 0) +} +func TableIndexAddKeyCount(builder *flatbuffers.Builder, keyCount uint32) { + builder.PrependUint32Slot(3, keyCount, 0) +} +func TableIndexAddUncompressedSize(builder *flatbuffers.Builder, uncompressedSize uint32) { + builder.PrependUint32Slot(4, uncompressedSize, 0) +} +func TableIndexAddOnDiskSize(builder *flatbuffers.Builder, onDiskSize uint32) { + builder.PrependUint32Slot(5, onDiskSize, 0) +} +func TableIndexAddStaleDataSize(builder *flatbuffers.Builder, staleDataSize uint32) { + builder.PrependUint32Slot(6, staleDataSize, 0) +} +func TableIndexEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { + return builder.EndObject() +} diff --git a/vendor/github.com/dgraph-io/badger/v3/fb/flatbuffer.fbs b/vendor/github.com/dgraph-io/badger/v3/fb/flatbuffer.fbs new file mode 100644 index 0000000000..af559dd4a4 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/fb/flatbuffer.fbs @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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. + */ + +namespace fb; + +table TableIndex { + offsets:[BlockOffset]; + bloom_filter:[ubyte]; + max_version:uint64; + key_count:uint32; + uncompressed_size:uint32; + on_disk_size:uint32; + stale_data_size:uint32; +} + +table BlockOffset { + key:[ubyte]; + offset:uint; + len:uint; +} + +root_type TableIndex; +root_type BlockOffset; diff --git a/vendor/github.com/dgraph-io/badger/v3/fb/gen.sh b/vendor/github.com/dgraph-io/badger/v3/fb/gen.sh new file mode 100644 index 0000000000..90d88b4a3a --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/fb/gen.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e + +## Install flatc if not present +## ref. https://google.github.io/flatbuffers/flatbuffers_guide_building.html +command -v flatc > /dev/null || { ./install_flatbuffers.sh ; } + +flatc --go flatbuffer.fbs +# Move files to the correct directory. +mv fb/* ./ +rmdir fb diff --git a/vendor/github.com/dgraph-io/badger/v3/fb/install_flatbuffers.sh b/vendor/github.com/dgraph-io/badger/v3/fb/install_flatbuffers.sh new file mode 100644 index 0000000000..ae07da7e24 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/fb/install_flatbuffers.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +set -e + +install_mac() { + command -v brew > /dev/null || \ + { echo "[ERROR]: 'brew' command not not found. Exiting" 1>&2; exit 1; } + brew install flatbuffers +} + +install_linux() { + for CMD in curl cmake g++ make; do + command -v $CMD > /dev/null || \ + { echo "[ERROR]: '$CMD' command not not found. Exiting" 1>&2; exit 1; } + done + + ## Create Temp Build Directory + BUILD_DIR=$(mktemp -d) + pushd $BUILD_DIR + + ## Fetch Latest Tarball + LATEST_VERSION=$(curl -s https://api.github.com/repos/google/flatbuffers/releases/latest | grep -oP '(?<=tag_name": ")[^"]+') + curl -sLO https://github.com/google/flatbuffers/archive/$LATEST_VERSION.tar.gz + tar xf $LATEST_VERSION.tar.gz + + ## Build Binaries + cd flatbuffers-${LATEST_VERSION#v} + cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release + make + ./flattests + cp flatc /usr/local/bin/flatc + + ## Cleanup Temp Build Directory + popd + rm -rf $BUILD_DIR +} + +SYSTEM=$(uname -s) + +case ${SYSTEM,,} in + linux) + sudo bash -c "$(declare -f install_linux); install_linux" + ;; + darwin) + install_mac + ;; +esac diff --git a/vendor/github.com/dgraph-io/badger/v3/histogram.go b/vendor/github.com/dgraph-io/badger/v3/histogram.go new file mode 100644 index 0000000000..d8c94bb7ad --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/histogram.go @@ -0,0 +1,169 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "fmt" + "math" +) + +// PrintHistogram builds and displays the key-value size histogram. +// When keyPrefix is set, only the keys that have prefix "keyPrefix" are +// considered for creating the histogram +func (db *DB) PrintHistogram(keyPrefix []byte) { + if db == nil { + fmt.Println("\nCannot build histogram: DB is nil.") + return + } + histogram := db.buildHistogram(keyPrefix) + fmt.Printf("Histogram of key sizes (in bytes)\n") + histogram.keySizeHistogram.printHistogram() + fmt.Printf("Histogram of value sizes (in bytes)\n") + histogram.valueSizeHistogram.printHistogram() +} + +// histogramData stores information about a histogram +type histogramData struct { + bins []int64 + countPerBin []int64 + totalCount int64 + min int64 + max int64 + sum int64 +} + +// sizeHistogram contains keySize histogram and valueSize histogram +type sizeHistogram struct { + keySizeHistogram, valueSizeHistogram histogramData +} + +// newSizeHistogram returns a new instance of keyValueSizeHistogram with +// properly initialized fields. +func newSizeHistogram() *sizeHistogram { + // TODO(ibrahim): find appropriate bin size. + keyBins := createHistogramBins(1, 16) + valueBins := createHistogramBins(1, 30) + return &sizeHistogram{ + keySizeHistogram: histogramData{ + bins: keyBins, + countPerBin: make([]int64, len(keyBins)+1), + max: math.MinInt64, + min: math.MaxInt64, + sum: 0, + }, + valueSizeHistogram: histogramData{ + bins: valueBins, + countPerBin: make([]int64, len(valueBins)+1), + max: math.MinInt64, + min: math.MaxInt64, + sum: 0, + }, + } +} + +// createHistogramBins creates bins for an histogram. The bin sizes are powers +// of two of the form [2^min_exponent, ..., 2^max_exponent]. +func createHistogramBins(minExponent, maxExponent uint32) []int64 { + var bins []int64 + for i := minExponent; i <= maxExponent; i++ { + bins = append(bins, int64(1)< histogram.max { + histogram.max = value + } + if value < histogram.min { + histogram.min = value + } + + histogram.sum += value + histogram.totalCount++ + + for index := 0; index <= len(histogram.bins); index++ { + // Allocate value in the last buckets if we reached the end of the Bounds array. + if index == len(histogram.bins) { + histogram.countPerBin[index]++ + break + } + + // Check if the value should be added to the "index" bin + if value < int64(histogram.bins[index]) { + histogram.countPerBin[index]++ + break + } + } +} + +// buildHistogram builds the key-value size histogram. +// When keyPrefix is set, only the keys that have prefix "keyPrefix" are +// considered for creating the histogram +func (db *DB) buildHistogram(keyPrefix []byte) *sizeHistogram { + txn := db.NewTransaction(false) + defer txn.Discard() + + itr := txn.NewIterator(DefaultIteratorOptions) + defer itr.Close() + + badgerHistogram := newSizeHistogram() + + // Collect key and value sizes. + for itr.Seek(keyPrefix); itr.ValidForPrefix(keyPrefix); itr.Next() { + item := itr.Item() + badgerHistogram.keySizeHistogram.Update(item.KeySize()) + badgerHistogram.valueSizeHistogram.Update(item.ValueSize()) + } + return badgerHistogram +} + +// printHistogram prints the histogram data in a human-readable format. +func (histogram histogramData) printHistogram() { + fmt.Printf("Total count: %d\n", histogram.totalCount) + fmt.Printf("Min value: %d\n", histogram.min) + fmt.Printf("Max value: %d\n", histogram.max) + fmt.Printf("Mean: %.2f\n", float64(histogram.sum)/float64(histogram.totalCount)) + fmt.Printf("%24s %9s\n", "Range", "Count") + + numBins := len(histogram.bins) + for index, count := range histogram.countPerBin { + if count == 0 { + continue + } + + // The last bin represents the bin that contains the range from + // the last bin up to infinity so it's processed differently than the + // other bins. + if index == len(histogram.countPerBin)-1 { + lowerBound := int(histogram.bins[numBins-1]) + fmt.Printf("[%10d, %10s) %9d\n", lowerBound, "infinity", count) + continue + } + + upperBound := int(histogram.bins[index]) + lowerBound := 0 + if index > 0 { + lowerBound = int(histogram.bins[index-1]) + } + + fmt.Printf("[%10d, %10d) %9d\n", lowerBound, upperBound, count) + } + fmt.Println() +} diff --git a/vendor/github.com/dgraph-io/badger/v3/iterator.go b/vendor/github.com/dgraph-io/badger/v3/iterator.go new file mode 100644 index 0000000000..409e8618a6 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/iterator.go @@ -0,0 +1,788 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bytes" + "fmt" + "hash/crc32" + "math" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/dgraph-io/badger/v3/table" + "github.com/dgraph-io/ristretto/z" + + "github.com/dgraph-io/badger/v3/y" +) + +type prefetchStatus uint8 + +const ( + prefetched prefetchStatus = iota + 1 +) + +// Item is returned during iteration. Both the Key() and Value() output is only valid until +// iterator.Next() is called. +type Item struct { + key []byte + vptr []byte + val []byte + version uint64 + expiresAt uint64 + + slice *y.Slice // Used only during prefetching. + next *Item + txn *Txn + + err error + wg sync.WaitGroup + status prefetchStatus + meta byte // We need to store meta to know about bitValuePointer. + userMeta byte +} + +// String returns a string representation of Item +func (item *Item) String() string { + return fmt.Sprintf("key=%q, version=%d, meta=%x", item.Key(), item.Version(), item.meta) +} + +// Key returns the key. +// +// Key is only valid as long as item is valid, or transaction is valid. If you need to use it +// outside its validity, please use KeyCopy. +func (item *Item) Key() []byte { + return item.key +} + +// KeyCopy returns a copy of the key of the item, writing it to dst slice. +// If nil is passed, or capacity of dst isn't sufficient, a new slice would be allocated and +// returned. +func (item *Item) KeyCopy(dst []byte) []byte { + return y.SafeCopy(dst, item.key) +} + +// Version returns the commit timestamp of the item. +func (item *Item) Version() uint64 { + return item.version +} + +// Value retrieves the value of the item from the value log. +// +// This method must be called within a transaction. Calling it outside a +// transaction is considered undefined behavior. If an iterator is being used, +// then Item.Value() is defined in the current iteration only, because items are +// reused. +// +// If you need to use a value outside a transaction, please use Item.ValueCopy +// instead, or copy it yourself. Value might change once discard or commit is called. +// Use ValueCopy if you want to do a Set after Get. +func (item *Item) Value(fn func(val []byte) error) error { + item.wg.Wait() + if item.status == prefetched { + if item.err == nil && fn != nil { + if err := fn(item.val); err != nil { + return err + } + } + return item.err + } + buf, cb, err := item.yieldItemValue() + defer runCallback(cb) + if err != nil { + return err + } + if fn != nil { + return fn(buf) + } + return nil +} + +// ValueCopy returns a copy of the value of the item from the value log, writing it to dst slice. +// If nil is passed, or capacity of dst isn't sufficient, a new slice would be allocated and +// returned. Tip: It might make sense to reuse the returned slice as dst argument for the next call. +// +// This function is useful in long running iterate/update transactions to avoid a write deadlock. +// See Github issue: https://github.com/dgraph-io/badger/issues/315 +func (item *Item) ValueCopy(dst []byte) ([]byte, error) { + item.wg.Wait() + if item.status == prefetched { + return y.SafeCopy(dst, item.val), item.err + } + buf, cb, err := item.yieldItemValue() + defer runCallback(cb) + return y.SafeCopy(dst, buf), err +} + +func (item *Item) hasValue() bool { + if item.meta == 0 && item.vptr == nil { + // key not found + return false + } + return true +} + +// IsDeletedOrExpired returns true if item contains deleted or expired value. +func (item *Item) IsDeletedOrExpired() bool { + return isDeletedOrExpired(item.meta, item.expiresAt) +} + +// DiscardEarlierVersions returns whether the item was created with the +// option to discard earlier versions of a key when multiple are available. +func (item *Item) DiscardEarlierVersions() bool { + return item.meta&bitDiscardEarlierVersions > 0 +} + +func (item *Item) yieldItemValue() ([]byte, func(), error) { + key := item.Key() // No need to copy. + if !item.hasValue() { + return nil, nil, nil + } + + if item.slice == nil { + item.slice = new(y.Slice) + } + + if (item.meta & bitValuePointer) == 0 { + val := item.slice.Resize(len(item.vptr)) + copy(val, item.vptr) + return val, nil, nil + } + + var vp valuePointer + vp.Decode(item.vptr) + db := item.txn.db + result, cb, err := db.vlog.Read(vp, item.slice) + if err != nil { + db.opt.Logger.Errorf("Unable to read: Key: %v, Version : %v, meta: %v, userMeta: %v"+ + " Error: %v", key, item.version, item.meta, item.userMeta, err) + var txn *Txn + if db.opt.managedTxns { + txn = db.NewTransactionAt(math.MaxUint64, false) + } else { + txn = db.NewTransaction(false) + } + defer txn.Discard() + + iopt := DefaultIteratorOptions + iopt.AllVersions = true + iopt.InternalAccess = true + iopt.PrefetchValues = false + + it := txn.NewKeyIterator(item.Key(), iopt) + defer it.Close() + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + var vp valuePointer + if item.meta&bitValuePointer > 0 { + vp.Decode(item.vptr) + } + db.opt.Logger.Errorf("Key: %v, Version : %v, meta: %v, userMeta: %v valuePointer: %+v", + item.Key(), item.version, item.meta, item.userMeta, vp) + } + } + // Don't return error if we cannot read the value. Just log the error. + return result, cb, nil +} + +func runCallback(cb func()) { + if cb != nil { + cb() + } +} + +func (item *Item) prefetchValue() { + val, cb, err := item.yieldItemValue() + defer runCallback(cb) + + item.err = err + item.status = prefetched + if val == nil { + return + } + buf := item.slice.Resize(len(val)) + copy(buf, val) + item.val = buf +} + +// EstimatedSize returns the approximate size of the key-value pair. +// +// This can be called while iterating through a store to quickly estimate the +// size of a range of key-value pairs (without fetching the corresponding +// values). +func (item *Item) EstimatedSize() int64 { + if !item.hasValue() { + return 0 + } + if (item.meta & bitValuePointer) == 0 { + return int64(len(item.key) + len(item.vptr)) + } + var vp valuePointer + vp.Decode(item.vptr) + return int64(vp.Len) // includes key length. +} + +// KeySize returns the size of the key. +// Exact size of the key is key + 8 bytes of timestamp +func (item *Item) KeySize() int64 { + return int64(len(item.key)) +} + +// ValueSize returns the approximate size of the value. +// +// This can be called to quickly estimate the size of a value without fetching +// it. +func (item *Item) ValueSize() int64 { + if !item.hasValue() { + return 0 + } + if (item.meta & bitValuePointer) == 0 { + return int64(len(item.vptr)) + } + var vp valuePointer + vp.Decode(item.vptr) + + klen := int64(len(item.key) + 8) // 8 bytes for timestamp. + // 6 bytes are for the approximate length of the header. Since header is encoded in varint, we + // cannot find the exact length of header without fetching it. + return int64(vp.Len) - klen - 6 - crc32.Size +} + +// UserMeta returns the userMeta set by the user. Typically, this byte, optionally set by the user +// is used to interpret the value. +func (item *Item) UserMeta() byte { + return item.userMeta +} + +// ExpiresAt returns a Unix time value indicating when the item will be +// considered expired. 0 indicates that the item will never expire. +func (item *Item) ExpiresAt() uint64 { + return item.expiresAt +} + +// TODO: Switch this to use linked list container in Go. +type list struct { + head *Item + tail *Item +} + +func (l *list) push(i *Item) { + i.next = nil + if l.tail == nil { + l.head = i + l.tail = i + return + } + l.tail.next = i + l.tail = i +} + +func (l *list) pop() *Item { + if l.head == nil { + return nil + } + i := l.head + if l.head == l.tail { + l.tail = nil + l.head = nil + } else { + l.head = i.next + } + i.next = nil + return i +} + +// IteratorOptions is used to set options when iterating over Badger key-value +// stores. +// +// This package provides DefaultIteratorOptions which contains options that +// should work for most applications. Consider using that as a starting point +// before customizing it for your own needs. +type IteratorOptions struct { + // PrefetchSize is the number of KV pairs to prefetch while iterating. + // Valid only if PrefetchValues is true. + PrefetchSize int + // PrefetchValues Indicates whether we should prefetch values during + // iteration and store them. + PrefetchValues bool + Reverse bool // Direction of iteration. False is forward, true is backward. + AllVersions bool // Fetch all valid versions of the same key. + InternalAccess bool // Used to allow internal access to badger keys. + + // The following option is used to narrow down the SSTables that iterator + // picks up. If Prefix is specified, only tables which could have this + // prefix are picked based on their range of keys. + prefixIsKey bool // If set, use the prefix for bloom filter lookup. + Prefix []byte // Only iterate over this given prefix. + SinceTs uint64 // Only read data that has version > SinceTs. +} + +func (opt *IteratorOptions) compareToPrefix(key []byte) int { + // We should compare key without timestamp. For example key - a[TS] might be > "aa" prefix. + key = y.ParseKey(key) + if len(key) > len(opt.Prefix) { + key = key[:len(opt.Prefix)] + } + return bytes.Compare(key, opt.Prefix) +} + +func (opt *IteratorOptions) pickTable(t table.TableInterface) bool { + // Ignore this table if its max version is less than the sinceTs. + if t.MaxVersion() < opt.SinceTs { + return false + } + if len(opt.Prefix) == 0 { + return true + } + if opt.compareToPrefix(t.Smallest()) > 0 { + return false + } + if opt.compareToPrefix(t.Biggest()) < 0 { + return false + } + // Bloom filter lookup would only work if opt.Prefix does NOT have the read + // timestamp as part of the key. + if opt.prefixIsKey && t.DoesNotHave(y.Hash(opt.Prefix)) { + return false + } + return true +} + +// pickTables picks the necessary table for the iterator. This function also assumes +// that the tables are sorted in the right order. +func (opt *IteratorOptions) pickTables(all []*table.Table) []*table.Table { + filterTables := func(tables []*table.Table) []*table.Table { + if opt.SinceTs > 0 { + tmp := tables[:0] + for _, t := range tables { + if t.MaxVersion() < opt.SinceTs { + continue + } + tmp = append(tmp, t) + } + tables = tmp + } + return tables + } + + if len(opt.Prefix) == 0 { + out := make([]*table.Table, len(all)) + copy(out, all) + return filterTables(out) + } + sIdx := sort.Search(len(all), func(i int) bool { + // table.Biggest >= opt.prefix + // if opt.Prefix < table.Biggest, then surely it is not in any of the preceding tables. + return opt.compareToPrefix(all[i].Biggest()) >= 0 + }) + if sIdx == len(all) { + // Not found. + return []*table.Table{} + } + + filtered := all[sIdx:] + if !opt.prefixIsKey { + eIdx := sort.Search(len(filtered), func(i int) bool { + return opt.compareToPrefix(filtered[i].Smallest()) > 0 + }) + out := make([]*table.Table, len(filtered[:eIdx])) + copy(out, filtered[:eIdx]) + return filterTables(out) + } + + // opt.prefixIsKey == true. This code is optimizing for opt.prefixIsKey part. + var out []*table.Table + hash := y.Hash(opt.Prefix) + for _, t := range filtered { + // When we encounter the first table whose smallest key is higher than opt.Prefix, we can + // stop. This is an IMPORTANT optimization, just considering how often we call + // NewKeyIterator. + if opt.compareToPrefix(t.Smallest()) > 0 { + // if table.Smallest > opt.Prefix, then this and all tables after this can be ignored. + break + } + // opt.Prefix is actually the key. So, we can run bloom filter checks + // as well. + if t.DoesNotHave(hash) { + continue + } + out = append(out, t) + } + return filterTables(out) +} + +// DefaultIteratorOptions contains default options when iterating over Badger key-value stores. +var DefaultIteratorOptions = IteratorOptions{ + PrefetchValues: true, + PrefetchSize: 100, + Reverse: false, + AllVersions: false, +} + +// Iterator helps iterating over the KV pairs in a lexicographically sorted order. +type Iterator struct { + iitr y.Iterator + txn *Txn + readTs uint64 + + opt IteratorOptions + item *Item + data list + waste list + + lastKey []byte // Used to skip over multiple versions of the same key. + + closed bool + scanned int // Used to estimate the size of data scanned by iterator. + + // ThreadId is an optional value that can be set to identify which goroutine created + // the iterator. It can be used, for example, to uniquely identify each of the + // iterators created by the stream interface + ThreadId int + + Alloc *z.Allocator +} + +// NewIterator returns a new iterator. Depending upon the options, either only keys, or both +// key-value pairs would be fetched. The keys are returned in lexicographically sorted order. +// Using prefetch is recommended if you're doing a long running iteration, for performance. +// +// Multiple Iterators: +// For a read-only txn, multiple iterators can be running simultaneously. However, for a read-write +// txn, iterators have the nuance of being a snapshot of the writes for the transaction at the time +// iterator was created. If writes are performed after an iterator is created, then that iterator +// will not be able to see those writes. Only writes performed before an iterator was created can be +// viewed. +func (txn *Txn) NewIterator(opt IteratorOptions) *Iterator { + if txn.discarded { + panic("Transaction has already been discarded") + } + if txn.db.IsClosed() { + panic(ErrDBClosed.Error()) + } + + // Keep track of the number of active iterators. + atomic.AddInt32(&txn.numIterators, 1) + + // TODO: If Prefix is set, only pick those memtables which have keys with + // the prefix. + tables, decr := txn.db.getMemTables() + defer decr() + txn.db.vlog.incrIteratorCount() + var iters []y.Iterator + if itr := txn.newPendingWritesIterator(opt.Reverse); itr != nil { + iters = append(iters, itr) + } + for i := 0; i < len(tables); i++ { + iters = append(iters, tables[i].sl.NewUniIterator(opt.Reverse)) + } + iters = txn.db.lc.appendIterators(iters, &opt) // This will increment references. + res := &Iterator{ + txn: txn, + iitr: table.NewMergeIterator(iters, opt.Reverse), + opt: opt, + readTs: txn.readTs, + } + return res +} + +// NewKeyIterator is just like NewIterator, but allows the user to iterate over all versions of a +// single key. Internally, it sets the Prefix option in provided opt, and uses that prefix to +// additionally run bloom filter lookups before picking tables from the LSM tree. +func (txn *Txn) NewKeyIterator(key []byte, opt IteratorOptions) *Iterator { + if len(opt.Prefix) > 0 { + panic("opt.Prefix should be nil for NewKeyIterator.") + } + opt.Prefix = key // This key must be without the timestamp. + opt.prefixIsKey = true + opt.AllVersions = true + return txn.NewIterator(opt) +} + +func (it *Iterator) newItem() *Item { + item := it.waste.pop() + if item == nil { + item = &Item{slice: new(y.Slice), txn: it.txn} + } + return item +} + +// Item returns pointer to the current key-value pair. +// This item is only valid until it.Next() gets called. +func (it *Iterator) Item() *Item { + tx := it.txn + tx.addReadKey(it.item.Key()) + return it.item +} + +// Valid returns false when iteration is done. +func (it *Iterator) Valid() bool { + if it.item == nil { + return false + } + if it.opt.prefixIsKey { + return bytes.Equal(it.item.key, it.opt.Prefix) + } + return bytes.HasPrefix(it.item.key, it.opt.Prefix) +} + +// ValidForPrefix returns false when iteration is done +// or when the current key is not prefixed by the specified prefix. +func (it *Iterator) ValidForPrefix(prefix []byte) bool { + return it.Valid() && bytes.HasPrefix(it.item.key, prefix) +} + +// Close would close the iterator. It is important to call this when you're done with iteration. +func (it *Iterator) Close() { + if it.closed { + return + } + it.closed = true + if it.iitr == nil { + atomic.AddInt32(&it.txn.numIterators, -1) + return + } + + it.iitr.Close() + // It is important to wait for the fill goroutines to finish. Otherwise, we might leave zombie + // goroutines behind, which are waiting to acquire file read locks after DB has been closed. + waitFor := func(l list) { + item := l.pop() + for item != nil { + item.wg.Wait() + item = l.pop() + } + } + waitFor(it.waste) + waitFor(it.data) + + // TODO: We could handle this error. + _ = it.txn.db.vlog.decrIteratorCount() + atomic.AddInt32(&it.txn.numIterators, -1) +} + +// Next would advance the iterator by one. Always check it.Valid() after a Next() +// to ensure you have access to a valid it.Item(). +func (it *Iterator) Next() { + if it.iitr == nil { + return + } + // Reuse current item + it.item.wg.Wait() // Just cleaner to wait before pushing to avoid doing ref counting. + it.scanned += len(it.item.key) + len(it.item.val) + len(it.item.vptr) + 2 + it.waste.push(it.item) + + // Set next item to current + it.item = it.data.pop() + for it.iitr.Valid() { + if it.parseItem() { + // parseItem calls one extra next. + // This is used to deal with the complexity of reverse iteration. + break + } + } +} + +func isDeletedOrExpired(meta byte, expiresAt uint64) bool { + if meta&bitDelete > 0 { + return true + } + if expiresAt == 0 { + return false + } + return expiresAt <= uint64(time.Now().Unix()) +} + +// parseItem is a complex function because it needs to handle both forward and reverse iteration +// implementation. We store keys such that their versions are sorted in descending order. This makes +// forward iteration efficient, but revese iteration complicated. This tradeoff is better because +// forward iteration is more common than reverse. It returns true, if either the iterator is invalid +// or it has pushed an item into it.data list, else it returns false. +// +// This function advances the iterator. +func (it *Iterator) parseItem() bool { + mi := it.iitr + key := mi.Key() + + setItem := func(item *Item) { + if it.item == nil { + it.item = item + } else { + it.data.push(item) + } + } + + isInternalKey := bytes.HasPrefix(key, badgerPrefix) + // Skip badger keys. + if !it.opt.InternalAccess && isInternalKey { + mi.Next() + return false + } + + // Skip any versions which are beyond the readTs. + version := y.ParseTs(key) + // Ignore everything that is above the readTs and below or at the sinceTs. + if version > it.readTs || (it.opt.SinceTs > 0 && version <= it.opt.SinceTs) { + mi.Next() + return false + } + + // Skip banned keys only if it does not have badger internal prefix. + if !isInternalKey && it.txn.db.isBanned(key) != nil { + mi.Next() + return false + } + + if it.opt.AllVersions { + // Return deleted or expired values also, otherwise user can't figure out + // whether the key was deleted. + item := it.newItem() + it.fill(item) + setItem(item) + mi.Next() + return true + } + + // If iterating in forward direction, then just checking the last key against current key would + // be sufficient. + if !it.opt.Reverse { + if y.SameKey(it.lastKey, key) { + mi.Next() + return false + } + // Only track in forward direction. + // We should update lastKey as soon as we find a different key in our snapshot. + // Consider keys: a 5, b 7 (del), b 5. When iterating, lastKey = a. + // Then we see b 7, which is deleted. If we don't store lastKey = b, we'll then return b 5, + // which is wrong. Therefore, update lastKey here. + it.lastKey = y.SafeCopy(it.lastKey, mi.Key()) + } + +FILL: + // If deleted, advance and return. + vs := mi.Value() + if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) { + mi.Next() + return false + } + + item := it.newItem() + it.fill(item) + // fill item based on current cursor position. All Next calls have returned, so reaching here + // means no Next was called. + + mi.Next() // Advance but no fill item yet. + if !it.opt.Reverse || !mi.Valid() { // Forward direction, or invalid. + setItem(item) + return true + } + + // Reverse direction. + nextTs := y.ParseTs(mi.Key()) + mik := y.ParseKey(mi.Key()) + if nextTs <= it.readTs && bytes.Equal(mik, item.key) { + // This is a valid potential candidate. + goto FILL + } + // Ignore the next candidate. Return the current one. + setItem(item) + return true +} + +func (it *Iterator) fill(item *Item) { + vs := it.iitr.Value() + item.meta = vs.Meta + item.userMeta = vs.UserMeta + item.expiresAt = vs.ExpiresAt + + item.version = y.ParseTs(it.iitr.Key()) + item.key = y.SafeCopy(item.key, y.ParseKey(it.iitr.Key())) + + item.vptr = y.SafeCopy(item.vptr, vs.Value) + item.val = nil + if it.opt.PrefetchValues { + item.wg.Add(1) + go func() { + // FIXME we are not handling errors here. + item.prefetchValue() + item.wg.Done() + }() + } +} + +func (it *Iterator) prefetch() { + prefetchSize := 2 + if it.opt.PrefetchValues && it.opt.PrefetchSize > 1 { + prefetchSize = it.opt.PrefetchSize + } + + i := it.iitr + var count int + it.item = nil + for i.Valid() { + if !it.parseItem() { + continue + } + count++ + if count == prefetchSize { + break + } + } +} + +// Seek would seek to the provided key if present. If absent, it would seek to the next +// smallest key greater than the provided key if iterating in the forward direction. +// Behavior would be reversed if iterating backwards. +func (it *Iterator) Seek(key []byte) { + if it.iitr == nil { + return + } + if len(key) > 0 { + it.txn.addReadKey(key) + } + for i := it.data.pop(); i != nil; i = it.data.pop() { + i.wg.Wait() + it.waste.push(i) + } + + it.lastKey = it.lastKey[:0] + if len(key) == 0 { + key = it.opt.Prefix + } + if len(key) == 0 { + it.iitr.Rewind() + it.prefetch() + return + } + + if !it.opt.Reverse { + key = y.KeyWithTs(key, it.txn.readTs) + } else { + key = y.KeyWithTs(key, 0) + } + it.iitr.Seek(key) + it.prefetch() +} + +// Rewind would rewind the iterator cursor all the way to zero-th position, which would be the +// smallest key if iterating forward, and largest if iterating backward. It does not keep track of +// whether the cursor started with a Seek(). +func (it *Iterator) Rewind() { + it.Seek(nil) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/key_registry.go b/vendor/github.com/dgraph-io/badger/v3/key_registry.go new file mode 100644 index 0000000000..306e9c1297 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/key_registry.go @@ -0,0 +1,424 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bytes" + "crypto/aes" + "crypto/rand" + "encoding/binary" + "hash/crc32" + "io" + "os" + "path/filepath" + "sync" + "time" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/y" +) + +const ( + // KeyRegistryFileName is the file name for the key registry file. + KeyRegistryFileName = "KEYREGISTRY" + // KeyRegistryRewriteFileName is the file name for the rewrite key registry file. + KeyRegistryRewriteFileName = "REWRITE-KEYREGISTRY" +) + +// SanityText is used to check whether the given user provided storage key is valid or not +var sanityText = []byte("Hello Badger") + +// KeyRegistry used to maintain all the data keys. +type KeyRegistry struct { + sync.RWMutex + dataKeys map[uint64]*pb.DataKey + lastCreated int64 //lastCreated is the timestamp(seconds) of the last data key generated. + nextKeyID uint64 + fp *os.File + opt KeyRegistryOptions +} + +type KeyRegistryOptions struct { + Dir string + ReadOnly bool + EncryptionKey []byte + EncryptionKeyRotationDuration time.Duration + InMemory bool +} + +// newKeyRegistry returns KeyRegistry. +func newKeyRegistry(opt KeyRegistryOptions) *KeyRegistry { + return &KeyRegistry{ + dataKeys: make(map[uint64]*pb.DataKey), + nextKeyID: 0, + opt: opt, + } +} + +// OpenKeyRegistry opens key registry if it exists, otherwise it'll create key registry +// and returns key registry. +func OpenKeyRegistry(opt KeyRegistryOptions) (*KeyRegistry, error) { + // sanity check the encryption key length. + if len(opt.EncryptionKey) > 0 { + switch len(opt.EncryptionKey) { + default: + return nil, y.Wrapf(ErrInvalidEncryptionKey, "During OpenKeyRegistry") + case 16, 24, 32: + break + } + } + // If db is opened in InMemory mode, we don't need to write key registry to the disk. + if opt.InMemory { + return newKeyRegistry(opt), nil + } + path := filepath.Join(opt.Dir, KeyRegistryFileName) + var flags y.Flags + if opt.ReadOnly { + flags |= y.ReadOnly + } else { + flags |= y.Sync + } + fp, err := y.OpenExistingFile(path, flags) + // OpenExistingFile just open file. + // So checking whether the file exist or not. If not + // We'll create new keyregistry. + if os.IsNotExist(err) { + // Creating new registry file if not exist. + kr := newKeyRegistry(opt) + if opt.ReadOnly { + return kr, nil + } + // Writing the key registry to the file. + if err := WriteKeyRegistry(kr, opt); err != nil { + return nil, y.Wrapf(err, "Error while writing key registry.") + } + fp, err = y.OpenExistingFile(path, flags) + if err != nil { + return nil, y.Wrapf(err, "Error while opening newly created key registry.") + } + } else if err != nil { + return nil, y.Wrapf(err, "Error while opening key registry.") + } + kr, err := readKeyRegistry(fp, opt) + if err != nil { + // This case happens only if the file is opened properly and + // not able to read. + fp.Close() + return nil, err + } + if opt.ReadOnly { + // We'll close the file in readonly mode. + return kr, fp.Close() + } + kr.fp = fp + return kr, nil +} + +// keyRegistryIterator reads all the datakey from the key registry +type keyRegistryIterator struct { + encryptionKey []byte + fp *os.File + // lenCrcBuf contains crc buf and data length to move forward. + lenCrcBuf [8]byte +} + +// newKeyRegistryIterator returns iterator which will allow you to iterate +// over the data key of the key registry. +func newKeyRegistryIterator(fp *os.File, encryptionKey []byte) (*keyRegistryIterator, error) { + return &keyRegistryIterator{ + encryptionKey: encryptionKey, + fp: fp, + lenCrcBuf: [8]byte{}, + }, validRegistry(fp, encryptionKey) +} + +// validRegistry checks that given encryption key is valid or not. +func validRegistry(fp *os.File, encryptionKey []byte) error { + iv := make([]byte, aes.BlockSize) + var err error + if _, err = fp.Read(iv); err != nil { + return y.Wrapf(err, "Error while reading IV for key registry.") + } + eSanityText := make([]byte, len(sanityText)) + if _, err = fp.Read(eSanityText); err != nil { + return y.Wrapf(err, "Error while reading sanity text.") + } + if len(encryptionKey) > 0 { + // Decrypting sanity text. + if eSanityText, err = y.XORBlockAllocate(eSanityText, encryptionKey, iv); err != nil { + return y.Wrapf(err, "During validRegistry") + } + } + // Check the given key is valid or not. + if !bytes.Equal(eSanityText, sanityText) { + return ErrEncryptionKeyMismatch + } + return nil +} + +func (kri *keyRegistryIterator) next() (*pb.DataKey, error) { + var err error + // Read crc buf and data length. + if _, err = kri.fp.Read(kri.lenCrcBuf[:]); err != nil { + // EOF means end of the iteration. + if err != io.EOF { + return nil, y.Wrapf(err, "While reading crc in keyRegistryIterator.next") + } + return nil, err + } + l := int64(binary.BigEndian.Uint32(kri.lenCrcBuf[0:4])) + // Read protobuf data. + data := make([]byte, l) + if _, err = kri.fp.Read(data); err != nil { + // EOF means end of the iteration. + if err != io.EOF { + return nil, y.Wrapf(err, "While reading protobuf in keyRegistryIterator.next") + } + return nil, err + } + // Check checksum. + if crc32.Checksum(data, y.CastagnoliCrcTable) != binary.BigEndian.Uint32(kri.lenCrcBuf[4:]) { + return nil, y.Wrapf(y.ErrChecksumMismatch, "Error while checking checksum for data key.") + } + dataKey := &pb.DataKey{} + if err = dataKey.Unmarshal(data); err != nil { + return nil, y.Wrapf(err, "While unmarshal of datakey in keyRegistryIterator.next") + } + if len(kri.encryptionKey) > 0 { + // Decrypt the key if the storage key exists. + if dataKey.Data, err = y.XORBlockAllocate(dataKey.Data, kri.encryptionKey, dataKey.Iv); err != nil { + return nil, y.Wrapf(err, "While decrypting datakey in keyRegistryIterator.next") + } + } + return dataKey, nil +} + +// readKeyRegistry will read the key registry file and build the key registry struct. +func readKeyRegistry(fp *os.File, opt KeyRegistryOptions) (*KeyRegistry, error) { + itr, err := newKeyRegistryIterator(fp, opt.EncryptionKey) + if err != nil { + return nil, err + } + kr := newKeyRegistry(opt) + var dk *pb.DataKey + dk, err = itr.next() + for err == nil && dk != nil { + if dk.KeyId > kr.nextKeyID { + // Set the maximum key ID for next key ID generation. + kr.nextKeyID = dk.KeyId + } + if dk.CreatedAt > kr.lastCreated { + // Set the last generated key timestamp. + kr.lastCreated = dk.CreatedAt + } + // No need to lock since we are building the initial state. + kr.dataKeys[dk.KeyId] = dk + // Forward the iterator. + dk, err = itr.next() + } + // We read all the key. So, Ignoring this error. + if err == io.EOF { + err = nil + } + return kr, err +} + +/* +Structure of Key Registry. ++-------------------+---------------------+--------------------+--------------+------------------+ +| IV | Sanity Text | DataKey1 | DataKey2 | ... | ++-------------------+---------------------+--------------------+--------------+------------------+ +*/ + +// WriteKeyRegistry will rewrite the existing key registry file with new one. +// It is okay to give closed key registry. Since, it's using only the datakey. +func WriteKeyRegistry(reg *KeyRegistry, opt KeyRegistryOptions) error { + buf := &bytes.Buffer{} + iv, err := y.GenerateIV() + y.Check(err) + // Encrypt sanity text if the encryption key is presents. + eSanity := sanityText + if len(opt.EncryptionKey) > 0 { + var err error + eSanity, err = y.XORBlockAllocate(eSanity, opt.EncryptionKey, iv) + if err != nil { + return y.Wrapf(err, "Error while encrpting sanity text in WriteKeyRegistry") + } + } + y.Check2(buf.Write(iv)) + y.Check2(buf.Write(eSanity)) + // Write all the datakeys to the buf. + for _, k := range reg.dataKeys { + // Writing the datakey to the given buffer. + if err := storeDataKey(buf, opt.EncryptionKey, k); err != nil { + return y.Wrapf(err, "Error while storing datakey in WriteKeyRegistry") + } + } + tmpPath := filepath.Join(opt.Dir, KeyRegistryRewriteFileName) + // Open temporary file to write the data and do atomic rename. + fp, err := y.OpenTruncFile(tmpPath, true) + if err != nil { + return y.Wrapf(err, "Error while opening tmp file in WriteKeyRegistry") + } + // Write buf to the disk. + if _, err = fp.Write(buf.Bytes()); err != nil { + // close the fd before returning error. We're not using defer + // because, for windows we need to close the fd explicitly before + // renaming. + fp.Close() + return y.Wrapf(err, "Error while writing buf in WriteKeyRegistry") + } + // In Windows the files should be closed before doing a Rename. + if err = fp.Close(); err != nil { + return y.Wrapf(err, "Error while closing tmp file in WriteKeyRegistry") + } + // Rename to the original file. + if err = os.Rename(tmpPath, filepath.Join(opt.Dir, KeyRegistryFileName)); err != nil { + return y.Wrapf(err, "Error while renaming file in WriteKeyRegistry") + } + // Sync Dir. + return syncDir(opt.Dir) +} + +// DataKey returns datakey of the given key id. +func (kr *KeyRegistry) DataKey(id uint64) (*pb.DataKey, error) { + kr.RLock() + defer kr.RUnlock() + if id == 0 { + // nil represent plain text. + return nil, nil + } + dk, ok := kr.dataKeys[id] + if !ok { + return nil, y.Wrapf(ErrInvalidDataKeyID, "Error for the KEY ID %d", id) + } + return dk, nil +} + +// LatestDataKey will give you the latest generated datakey based on the rotation +// period. If the last generated datakey lifetime exceeds the rotation period. +// It'll create new datakey. +func (kr *KeyRegistry) LatestDataKey() (*pb.DataKey, error) { + if len(kr.opt.EncryptionKey) == 0 { + // nil is for no encryption. + return nil, nil + } + // validKey return datakey if the last generated key duration less than + // rotation duration. + validKey := func() (*pb.DataKey, bool) { + // Time diffrence from the last generated time. + diff := time.Since(time.Unix(kr.lastCreated, 0)) + if diff < kr.opt.EncryptionKeyRotationDuration { + return kr.dataKeys[kr.nextKeyID], true + } + return nil, false + } + kr.RLock() + key, valid := validKey() + kr.RUnlock() + if valid { + // If less than EncryptionKeyRotationDuration, returns the last generated key. + return key, nil + } + kr.Lock() + defer kr.Unlock() + // Key might have generated by another go routine. So, + // checking once again. + key, valid = validKey() + if valid { + return key, nil + } + k := make([]byte, len(kr.opt.EncryptionKey)) + iv, err := y.GenerateIV() + if err != nil { + return nil, err + } + _, err = rand.Read(k) + if err != nil { + return nil, err + } + // Otherwise Increment the KeyID and generate new datakey. + kr.nextKeyID++ + dk := &pb.DataKey{ + KeyId: kr.nextKeyID, + Data: k, + CreatedAt: time.Now().Unix(), + Iv: iv, + } + // Don't store the datakey on file if badger is running in InMemory mode. + if !kr.opt.InMemory { + // Store the datekey. + buf := &bytes.Buffer{} + if err = storeDataKey(buf, kr.opt.EncryptionKey, dk); err != nil { + return nil, err + } + // Persist the datakey to the disk + if _, err = kr.fp.Write(buf.Bytes()); err != nil { + return nil, err + } + } + // storeDatakey encrypts the datakey So, placing un-encrypted key in the memory. + dk.Data = k + kr.lastCreated = dk.CreatedAt + kr.dataKeys[kr.nextKeyID] = dk + return dk, nil +} + +// Close closes the key registry. +func (kr *KeyRegistry) Close() error { + if !(kr.opt.ReadOnly || kr.opt.InMemory) { + return kr.fp.Close() + } + return nil +} + +// storeDataKey stores datakey in an encrypted format in the given buffer. If storage key preset. +func storeDataKey(buf *bytes.Buffer, storageKey []byte, k *pb.DataKey) error { + // xor will encrypt the IV and xor with the given data. + // It'll used for both encryption and decryption. + xor := func() error { + if len(storageKey) == 0 { + return nil + } + var err error + k.Data, err = y.XORBlockAllocate(k.Data, storageKey, k.Iv) + return err + } + // In memory datakey will be plain text so encrypting before storing to the disk. + var err error + if err = xor(); err != nil { + return y.Wrapf(err, "Error while encrypting datakey in storeDataKey") + } + var data []byte + if data, err = k.Marshal(); err != nil { + err = y.Wrapf(err, "Error while marshaling datakey in storeDataKey") + var err2 error + // decrypting the datakey back. + if err2 = xor(); err2 != nil { + return y.Wrapf(err, + y.Wrapf(err2, "Error while decrypting datakey in storeDataKey").Error()) + } + return err + } + var lenCrcBuf [8]byte + binary.BigEndian.PutUint32(lenCrcBuf[0:4], uint32(len(data))) + binary.BigEndian.PutUint32(lenCrcBuf[4:8], crc32.Checksum(data, y.CastagnoliCrcTable)) + y.Check2(buf.Write(lenCrcBuf[:])) + y.Check2(buf.Write(data)) + // Decrypting the datakey back since we're using the pointer. + return xor() +} diff --git a/vendor/github.com/dgraph-io/badger/v3/level_handler.go b/vendor/github.com/dgraph-io/badger/v3/level_handler.go new file mode 100644 index 0000000000..669c097f4f --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/level_handler.go @@ -0,0 +1,353 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "fmt" + "sort" + "sync" + + "github.com/dgraph-io/badger/v3/table" + "github.com/dgraph-io/badger/v3/y" +) + +type levelHandler struct { + // Guards tables, totalSize. + sync.RWMutex + + // For level >= 1, tables are sorted by key ranges, which do not overlap. + // For level 0, tables are sorted by time. + // For level 0, newest table are at the back. Compact the oldest one first, which is at the front. + tables []*table.Table + totalSize int64 + totalStaleSize int64 + + // The following are initialized once and const. + level int + strLevel string + db *DB +} + +func (s *levelHandler) isLastLevel() bool { + return s.level == s.db.opt.MaxLevels-1 +} + +func (s *levelHandler) getTotalStaleSize() int64 { + s.RLock() + defer s.RUnlock() + return s.totalStaleSize +} + +func (s *levelHandler) getTotalSize() int64 { + s.RLock() + defer s.RUnlock() + return s.totalSize +} + +// initTables replaces s.tables with given tables. This is done during loading. +func (s *levelHandler) initTables(tables []*table.Table) { + s.Lock() + defer s.Unlock() + + s.tables = tables + s.totalSize = 0 + s.totalStaleSize = 0 + for _, t := range tables { + s.addSize(t) + } + + if s.level == 0 { + // Key range will overlap. Just sort by fileID in ascending order + // because newer tables are at the end of level 0. + sort.Slice(s.tables, func(i, j int) bool { + return s.tables[i].ID() < s.tables[j].ID() + }) + } else { + // Sort tables by keys. + sort.Slice(s.tables, func(i, j int) bool { + return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0 + }) + } +} + +// deleteTables remove tables idx0, ..., idx1-1. +func (s *levelHandler) deleteTables(toDel []*table.Table) error { + s.Lock() // s.Unlock() below + + toDelMap := make(map[uint64]struct{}) + for _, t := range toDel { + toDelMap[t.ID()] = struct{}{} + } + + // Make a copy as iterators might be keeping a slice of tables. + var newTables []*table.Table + for _, t := range s.tables { + _, found := toDelMap[t.ID()] + if !found { + newTables = append(newTables, t) + continue + } + s.subtractSize(t) + } + s.tables = newTables + + s.Unlock() // Unlock s _before_ we DecrRef our tables, which can be slow. + + return decrRefs(toDel) +} + +// replaceTables will replace tables[left:right] with newTables. Note this EXCLUDES tables[right]. +// You must call decr() to delete the old tables _after_ writing the update to the manifest. +func (s *levelHandler) replaceTables(toDel, toAdd []*table.Table) error { + // Need to re-search the range of tables in this level to be replaced as other goroutines might + // be changing it as well. (They can't touch our tables, but if they add/remove other tables, + // the indices get shifted around.) + s.Lock() // We s.Unlock() below. + + toDelMap := make(map[uint64]struct{}) + for _, t := range toDel { + toDelMap[t.ID()] = struct{}{} + } + var newTables []*table.Table + for _, t := range s.tables { + _, found := toDelMap[t.ID()] + if !found { + newTables = append(newTables, t) + continue + } + s.subtractSize(t) + } + + // Increase totalSize first. + for _, t := range toAdd { + s.addSize(t) + t.IncrRef() + newTables = append(newTables, t) + } + + // Assign tables. + s.tables = newTables + sort.Slice(s.tables, func(i, j int) bool { + return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0 + }) + s.Unlock() // s.Unlock before we DecrRef tables -- that can be slow. + return decrRefs(toDel) +} + +// addTable adds toAdd table to levelHandler. Normally when we add tables to levelHandler, we sort +// tables based on table.Smallest. This is required for correctness of the system. But in case of +// stream writer this can be avoided. We can just add tables to levelHandler's table list +// and after all addTable calls, we can sort table list(check sortTable method). +// NOTE: levelHandler.sortTables() should be called after call addTable calls are done. +func (s *levelHandler) addTable(t *table.Table) { + s.Lock() + defer s.Unlock() + + s.addSize(t) // Increase totalSize first. + t.IncrRef() + s.tables = append(s.tables, t) +} + +// sortTables sorts tables of levelHandler based on table.Smallest. +// Normally it should be called after all addTable calls. +func (s *levelHandler) sortTables() { + s.RLock() + defer s.RUnlock() + + sort.Slice(s.tables, func(i, j int) bool { + return y.CompareKeys(s.tables[i].Smallest(), s.tables[j].Smallest()) < 0 + }) +} + +func decrRefs(tables []*table.Table) error { + for _, table := range tables { + if err := table.DecrRef(); err != nil { + return err + } + } + return nil +} + +func newLevelHandler(db *DB, level int) *levelHandler { + return &levelHandler{ + level: level, + strLevel: fmt.Sprintf("l%d", level), + db: db, + } +} + +// tryAddLevel0Table returns true if ok and no stalling. +func (s *levelHandler) tryAddLevel0Table(t *table.Table) bool { + y.AssertTrue(s.level == 0) + // Need lock as we may be deleting the first table during a level 0 compaction. + s.Lock() + defer s.Unlock() + // Stall (by returning false) if we are above the specified stall setting for L0. + if len(s.tables) >= s.db.opt.NumLevelZeroTablesStall { + return false + } + + s.tables = append(s.tables, t) + t.IncrRef() + s.addSize(t) + + return true +} + +// This should be called while holding the lock on the level. +func (s *levelHandler) addSize(t *table.Table) { + s.totalSize += t.Size() + s.totalStaleSize += int64(t.StaleDataSize()) +} + +// This should be called while holding the lock on the level. +func (s *levelHandler) subtractSize(t *table.Table) { + s.totalSize -= t.Size() + s.totalStaleSize -= int64(t.StaleDataSize()) +} +func (s *levelHandler) numTables() int { + s.RLock() + defer s.RUnlock() + return len(s.tables) +} + +func (s *levelHandler) close() error { + s.RLock() + defer s.RUnlock() + var err error + for _, t := range s.tables { + if closeErr := t.Close(-1); closeErr != nil && err == nil { + err = closeErr + } + } + return y.Wrap(err, "levelHandler.close") +} + +// getTableForKey acquires a read-lock to access s.tables. It returns a list of tableHandlers. +func (s *levelHandler) getTableForKey(key []byte) ([]*table.Table, func() error) { + s.RLock() + defer s.RUnlock() + + if s.level == 0 { + // For level 0, we need to check every table. Remember to make a copy as s.tables may change + // once we exit this function, and we don't want to lock s.tables while seeking in tables. + // CAUTION: Reverse the tables. + out := make([]*table.Table, 0, len(s.tables)) + for i := len(s.tables) - 1; i >= 0; i-- { + out = append(out, s.tables[i]) + s.tables[i].IncrRef() + } + return out, func() error { + for _, t := range out { + if err := t.DecrRef(); err != nil { + return err + } + } + return nil + } + } + // For level >= 1, we can do a binary search as key range does not overlap. + idx := sort.Search(len(s.tables), func(i int) bool { + return y.CompareKeys(s.tables[i].Biggest(), key) >= 0 + }) + if idx >= len(s.tables) { + // Given key is strictly > than every element we have. + return nil, func() error { return nil } + } + tbl := s.tables[idx] + tbl.IncrRef() + return []*table.Table{tbl}, tbl.DecrRef +} + +// get returns value for a given key or the key after that. If not found, return nil. +func (s *levelHandler) get(key []byte) (y.ValueStruct, error) { + tables, decr := s.getTableForKey(key) + keyNoTs := y.ParseKey(key) + + hash := y.Hash(keyNoTs) + var maxVs y.ValueStruct + for _, th := range tables { + if th.DoesNotHave(hash) { + y.NumLSMBloomHitsAdd(s.db.opt.MetricsEnabled, s.strLevel, 1) + continue + } + + it := th.NewIterator(0) + defer it.Close() + + y.NumLSMGetsAdd(s.db.opt.MetricsEnabled, s.strLevel, 1) + it.Seek(key) + if !it.Valid() { + continue + } + if y.SameKey(key, it.Key()) { + if version := y.ParseTs(it.Key()); maxVs.Version < version { + maxVs = it.ValueCopy() + maxVs.Version = version + } + } + } + return maxVs, decr() +} + +// appendIterators appends iterators to an array of iterators, for merging. +// Note: This obtains references for the table handlers. Remember to close these iterators. +func (s *levelHandler) appendIterators(iters []y.Iterator, opt *IteratorOptions) []y.Iterator { + s.RLock() + defer s.RUnlock() + + var topt int + if opt.Reverse { + topt = table.REVERSED + } + if s.level == 0 { + // Remember to add in reverse order! + // The newer table at the end of s.tables should be added first as it takes precedence. + // Level 0 tables are not in key sorted order, so we need to consider them one by one. + var out []*table.Table + for _, t := range s.tables { + if opt.pickTable(t) { + out = append(out, t) + } + } + return appendIteratorsReversed(iters, out, topt) + } + + tables := opt.pickTables(s.tables) + if len(tables) == 0 { + return iters + } + return append(iters, table.NewConcatIterator(tables, topt)) +} + +type levelHandlerRLocked struct{} + +// overlappingTables returns the tables that intersect with key range. Returns a half-interval. +// This function should already have acquired a read lock, and this is so important the caller must +// pass an empty parameter declaring such. +func (s *levelHandler) overlappingTables(_ levelHandlerRLocked, kr keyRange) (int, int) { + if len(kr.left) == 0 || len(kr.right) == 0 { + return 0, 0 + } + left := sort.Search(len(s.tables), func(i int) bool { + return y.CompareKeys(kr.left, s.tables[i].Biggest()) <= 0 + }) + right := sort.Search(len(s.tables), func(i int) bool { + return y.CompareKeys(kr.right, s.tables[i].Smallest()) < 0 + }) + return left, right +} diff --git a/vendor/github.com/dgraph-io/badger/v3/levels.go b/vendor/github.com/dgraph-io/badger/v3/levels.go new file mode 100644 index 0000000000..1c625d1b34 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/levels.go @@ -0,0 +1,1761 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "math" + "math/rand" + "os" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + otrace "go.opencensus.io/trace" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/table" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + "github.com/pkg/errors" +) + +type levelsController struct { + nextFileID uint64 // Atomic + l0stallsMs int64 // Atomic + + // The following are initialized once and const. + levels []*levelHandler + kv *DB + + cstatus compactStatus +} + +// revertToManifest checks that all necessary table files exist and removes all table files not +// referenced by the manifest. idMap is a set of table file id's that were read from the directory +// listing. +func revertToManifest(kv *DB, mf *Manifest, idMap map[uint64]struct{}) error { + // 1. Check all files in manifest exist. + for id := range mf.Tables { + if _, ok := idMap[id]; !ok { + return fmt.Errorf("file does not exist for table %d", id) + } + } + + // 2. Delete files that shouldn't exist. + for id := range idMap { + if _, ok := mf.Tables[id]; !ok { + kv.opt.Debugf("Table file %d not referenced in MANIFEST\n", id) + filename := table.NewFilename(id, kv.opt.Dir) + if err := os.Remove(filename); err != nil { + return y.Wrapf(err, "While removing table %d", id) + } + } + } + + return nil +} + +func newLevelsController(db *DB, mf *Manifest) (*levelsController, error) { + y.AssertTrue(db.opt.NumLevelZeroTablesStall > db.opt.NumLevelZeroTables) + s := &levelsController{ + kv: db, + levels: make([]*levelHandler, db.opt.MaxLevels), + } + s.cstatus.tables = make(map[uint64]struct{}) + s.cstatus.levels = make([]*levelCompactStatus, db.opt.MaxLevels) + + for i := 0; i < db.opt.MaxLevels; i++ { + s.levels[i] = newLevelHandler(db, i) + s.cstatus.levels[i] = new(levelCompactStatus) + } + + if db.opt.InMemory { + return s, nil + } + // Compare manifest against directory, check for existent/non-existent files, and remove. + if err := revertToManifest(db, mf, getIDMap(db.opt.Dir)); err != nil { + return nil, err + } + + var mu sync.Mutex + tables := make([][]*table.Table, db.opt.MaxLevels) + var maxFileID uint64 + + // We found that using 3 goroutines allows disk throughput to be utilized to its max. + // Disk utilization is the main thing we should focus on, while trying to read the data. That's + // the one factor that remains constant between HDD and SSD. + throttle := y.NewThrottle(3) + + start := time.Now() + var numOpened int32 + tick := time.NewTicker(3 * time.Second) + defer tick.Stop() + + for fileID, tf := range mf.Tables { + fname := table.NewFilename(fileID, db.opt.Dir) + select { + case <-tick.C: + db.opt.Infof("%d tables out of %d opened in %s\n", atomic.LoadInt32(&numOpened), + len(mf.Tables), time.Since(start).Round(time.Millisecond)) + default: + } + if err := throttle.Do(); err != nil { + closeAllTables(tables) + return nil, err + } + if fileID > maxFileID { + maxFileID = fileID + } + go func(fname string, tf TableManifest) { + var rerr error + defer func() { + throttle.Done(rerr) + atomic.AddInt32(&numOpened, 1) + }() + dk, err := db.registry.DataKey(tf.KeyID) + if err != nil { + rerr = y.Wrapf(err, "Error while reading datakey") + return + } + topt := buildTableOptions(db) + // Explicitly set Compression and DataKey based on how the table was generated. + topt.Compression = tf.Compression + topt.DataKey = dk + + mf, err := z.OpenMmapFile(fname, db.opt.getFileFlags(), 0) + if err != nil { + rerr = y.Wrapf(err, "Opening file: %q", fname) + return + } + t, err := table.OpenTable(mf, topt) + if err != nil { + if strings.HasPrefix(err.Error(), "CHECKSUM_MISMATCH:") { + db.opt.Errorf(err.Error()) + db.opt.Errorf("Ignoring table %s", mf.Fd.Name()) + // Do not set rerr. We will continue without this table. + } else { + rerr = y.Wrapf(err, "Opening table: %q", fname) + } + return + } + + mu.Lock() + tables[tf.Level] = append(tables[tf.Level], t) + mu.Unlock() + }(fname, tf) + } + if err := throttle.Finish(); err != nil { + closeAllTables(tables) + return nil, err + } + db.opt.Infof("All %d tables opened in %s\n", atomic.LoadInt32(&numOpened), + time.Since(start).Round(time.Millisecond)) + s.nextFileID = maxFileID + 1 + for i, tbls := range tables { + s.levels[i].initTables(tbls) + } + + // Make sure key ranges do not overlap etc. + if err := s.validate(); err != nil { + _ = s.cleanupLevels() + return nil, y.Wrap(err, "Level validation") + } + + // Sync directory (because we have at least removed some files, or previously created the + // manifest file). + if err := syncDir(db.opt.Dir); err != nil { + _ = s.close() + return nil, err + } + + return s, nil +} + +// Closes the tables, for cleanup in newLevelsController. (We Close() instead of using DecrRef() +// because that would delete the underlying files.) We ignore errors, which is OK because tables +// are read-only. +func closeAllTables(tables [][]*table.Table) { + for _, tableSlice := range tables { + for _, table := range tableSlice { + _ = table.Close(-1) + } + } +} + +func (s *levelsController) cleanupLevels() error { + var firstErr error + for _, l := range s.levels { + if err := l.close(); err != nil && firstErr == nil { + firstErr = err + } + } + return firstErr +} + +// dropTree picks all tables from all levels, creates a manifest changeset, +// applies it, and then decrements the refs of these tables, which would result +// in their deletion. +func (s *levelsController) dropTree() (int, error) { + // First pick all tables, so we can create a manifest changelog. + var all []*table.Table + for _, l := range s.levels { + l.RLock() + all = append(all, l.tables...) + l.RUnlock() + } + if len(all) == 0 { + return 0, nil + } + + // Generate the manifest changes. + changes := []*pb.ManifestChange{} + for _, table := range all { + // Add a delete change only if the table is not in memory. + if !table.IsInmemory { + changes = append(changes, newDeleteChange(table.ID())) + } + } + changeSet := pb.ManifestChangeSet{Changes: changes} + if err := s.kv.manifest.addChanges(changeSet.Changes); err != nil { + return 0, err + } + + // Now that manifest has been successfully written, we can delete the tables. + for _, l := range s.levels { + l.Lock() + l.totalSize = 0 + l.tables = l.tables[:0] + l.Unlock() + } + for _, table := range all { + if err := table.DecrRef(); err != nil { + return 0, err + } + } + return len(all), nil +} + +// dropPrefix runs a L0->L1 compaction, and then runs same level compaction on the rest of the +// levels. For L0->L1 compaction, it runs compactions normally, but skips over +// all the keys with the provided prefix. +// For Li->Li compactions, it picks up the tables which would have the prefix. The +// tables who only have keys with this prefix are quickly dropped. The ones which have other keys +// are run through MergeIterator and compacted to create new tables. All the mechanisms of +// compactions apply, i.e. level sizes and MANIFEST are updated as in the normal flow. +func (s *levelsController) dropPrefixes(prefixes [][]byte) error { + opt := s.kv.opt + // Iterate levels in the reverse order because if we were to iterate from + // lower level (say level 0) to a higher level (say level 3) we could have + // a state in which level 0 is compacted and an older version of a key exists in lower level. + // At this point, if someone creates an iterator, they would see an old + // value for a key from lower levels. Iterating in reverse order ensures we + // drop the oldest data first so that lookups never return stale data. + for i := len(s.levels) - 1; i >= 0; i-- { + l := s.levels[i] + + l.RLock() + if l.level == 0 { + size := len(l.tables) + l.RUnlock() + + if size > 0 { + cp := compactionPriority{ + level: 0, + score: 1.74, + // A unique number greater than 1.0 does two things. Helps identify this + // function in logs, and forces a compaction. + dropPrefixes: prefixes, + } + if err := s.doCompact(174, cp); err != nil { + opt.Warningf("While compacting level 0: %v", err) + return nil + } + } + continue + } + + // Build a list of compaction tableGroups affecting all the prefixes we + // need to drop. We need to build tableGroups that satisfy the invariant that + // bottom tables are consecutive. + // tableGroup contains groups of consecutive tables. + var tableGroups [][]*table.Table + var tableGroup []*table.Table + + finishGroup := func() { + if len(tableGroup) > 0 { + tableGroups = append(tableGroups, tableGroup) + tableGroup = nil + } + } + + for _, table := range l.tables { + if containsAnyPrefixes(table, prefixes) { + tableGroup = append(tableGroup, table) + } else { + finishGroup() + } + } + finishGroup() + + l.RUnlock() + + if len(tableGroups) == 0 { + continue + } + _, span := otrace.StartSpan(context.Background(), "Badger.Compaction") + span.Annotatef(nil, "Compaction level: %v", l.level) + span.Annotatef(nil, "Drop Prefixes: %v", prefixes) + defer span.End() + opt.Infof("Dropping prefix at level %d (%d tableGroups)", l.level, len(tableGroups)) + for _, operation := range tableGroups { + cd := compactDef{ + span: span, + thisLevel: l, + nextLevel: l, + top: nil, + bot: operation, + dropPrefixes: prefixes, + t: s.levelTargets(), + } + cd.t.baseLevel = l.level + if err := s.runCompactDef(-1, l.level, cd); err != nil { + opt.Warningf("While running compact def: %+v. Error: %v", cd, err) + return err + } + } + } + return nil +} + +func (s *levelsController) startCompact(lc *z.Closer) { + n := s.kv.opt.NumCompactors + lc.AddRunning(n - 1) + for i := 0; i < n; i++ { + go s.runCompactor(i, lc) + } +} + +type targets struct { + baseLevel int + targetSz []int64 + fileSz []int64 +} + +// levelTargets calculates the targets for levels in the LSM tree. The idea comes from Dynamic Level +// Sizes ( https://rocksdb.org/blog/2015/07/23/dynamic-level.html ) in RocksDB. The sizes of levels +// are calculated based on the size of the lowest level, typically L6. So, if L6 size is 1GB, then +// L5 target size is 100MB, L4 target size is 10MB and so on. +// +// L0 files don't automatically go to L1. Instead, they get compacted to Lbase, where Lbase is +// chosen based on the first level which is non-empty from top (check L1 through L6). For an empty +// DB, that would be L6. So, L0 compactions go to L6, then L5, L4 and so on. +// +// Lbase is advanced to the upper levels when its target size exceeds BaseLevelSize. For +// example, when L6 reaches 1.1GB, then L4 target sizes becomes 11MB, thus exceeding the +// BaseLevelSize of 10MB. L3 would then become the new Lbase, with a target size of 1MB < +// BaseLevelSize. +func (s *levelsController) levelTargets() targets { + adjust := func(sz int64) int64 { + if sz < s.kv.opt.BaseLevelSize { + return s.kv.opt.BaseLevelSize + } + return sz + } + + t := targets{ + targetSz: make([]int64, len(s.levels)), + fileSz: make([]int64, len(s.levels)), + } + // DB size is the size of the last level. + dbSize := s.lastLevel().getTotalSize() + for i := len(s.levels) - 1; i > 0; i-- { + ltarget := adjust(dbSize) + t.targetSz[i] = ltarget + if t.baseLevel == 0 && ltarget <= s.kv.opt.BaseLevelSize { + t.baseLevel = i + } + dbSize /= int64(s.kv.opt.LevelSizeMultiplier) + } + + tsz := s.kv.opt.BaseTableSize + for i := 0; i < len(s.levels); i++ { + if i == 0 { + // Use MemTableSize for Level 0. Because at Level 0, we stop compactions based on the + // number of tables, not the size of the level. So, having a 1:1 size ratio between + // memtable size and the size of L0 files is better than churning out 32 files per + // memtable (assuming 64MB MemTableSize and 2MB BaseTableSize). + t.fileSz[i] = s.kv.opt.MemTableSize + } else if i <= t.baseLevel { + t.fileSz[i] = tsz + } else { + tsz *= int64(s.kv.opt.TableSizeMultiplier) + t.fileSz[i] = tsz + } + } + + // Bring the base level down to the last empty level. + for i := t.baseLevel + 1; i < len(s.levels)-1; i++ { + if s.levels[i].getTotalSize() > 0 { + break + } + t.baseLevel = i + } + + // If the base level is empty and the next level size is less than the + // target size, pick the next level as the base level. + b := t.baseLevel + lvl := s.levels + if b < len(lvl)-1 && lvl[b].getTotalSize() == 0 && lvl[b+1].getTotalSize() < t.targetSz[b+1] { + t.baseLevel++ + } + return t +} + +func (s *levelsController) runCompactor(id int, lc *z.Closer) { + defer lc.Done() + + randomDelay := time.NewTimer(time.Duration(rand.Int31n(1000)) * time.Millisecond) + select { + case <-randomDelay.C: + case <-lc.HasBeenClosed(): + randomDelay.Stop() + return + } + + moveL0toFront := func(prios []compactionPriority) []compactionPriority { + idx := -1 + for i, p := range prios { + if p.level == 0 { + idx = i + break + } + } + // If idx == -1, we didn't find L0. + // If idx == 0, then we don't need to do anything. L0 is already at the front. + if idx > 0 { + out := append([]compactionPriority{}, prios[idx]) + out = append(out, prios[:idx]...) + out = append(out, prios[idx+1:]...) + return out + } + return prios + } + + run := func(p compactionPriority) bool { + err := s.doCompact(id, p) + switch err { + case nil: + return true + case errFillTables: + // pass + default: + s.kv.opt.Warningf("While running doCompact: %v\n", err) + } + return false + } + runOnce := func() bool { + prios := s.pickCompactLevels() + if id == 0 { + // Worker ID zero prefers to compact L0 always. + prios = moveL0toFront(prios) + } + for _, p := range prios { + if id == 0 && p.level == 0 { + // Allow worker zero to run level 0, irrespective of its adjusted score. + } else if p.adjusted < 1.0 { + break + } + if run(p) { + return true + } + } + + return false + } + + tryLmaxToLmaxCompaction := func() { + p := compactionPriority{ + level: s.lastLevel().level, + t: s.levelTargets(), + } + run(p) + + } + count := 0 + ticker := time.NewTicker(50 * time.Millisecond) + defer ticker.Stop() + for { + select { + // Can add a done channel or other stuff. + case <-ticker.C: + count++ + // Each ticker is 50ms so 50*200=10seconds. + if s.kv.opt.LmaxCompaction && id == 2 && count >= 200 { + tryLmaxToLmaxCompaction() + count = 0 + } else { + runOnce() + } + case <-lc.HasBeenClosed(): + return + } + } +} + +type compactionPriority struct { + level int + score float64 + adjusted float64 + dropPrefixes [][]byte + t targets +} + +func (s *levelsController) lastLevel() *levelHandler { + return s.levels[len(s.levels)-1] +} + +// pickCompactLevel determines which level to compact. +// Based on: https://github.com/facebook/rocksdb/wiki/Leveled-Compaction +func (s *levelsController) pickCompactLevels() (prios []compactionPriority) { + t := s.levelTargets() + addPriority := func(level int, score float64) { + pri := compactionPriority{ + level: level, + score: score, + adjusted: score, + t: t, + } + prios = append(prios, pri) + } + + // Add L0 priority based on the number of tables. + addPriority(0, float64(s.levels[0].numTables())/float64(s.kv.opt.NumLevelZeroTables)) + + // All other levels use size to calculate priority. + for i := 1; i < len(s.levels); i++ { + // Don't consider those tables that are already being compacted right now. + delSize := s.cstatus.delSize(i) + + l := s.levels[i] + sz := l.getTotalSize() - delSize + addPriority(i, float64(sz)/float64(t.targetSz[i])) + } + y.AssertTrue(len(prios) == len(s.levels)) + + // The following code is borrowed from PebbleDB and results in healthier LSM tree structure. + // If Li-1 has score > 1.0, then we'll divide Li-1 score by Li. If Li score is >= 1.0, then Li-1 + // score is reduced, which means we'll prioritize the compaction of lower levels (L5, L4 and so + // on) over the higher levels (L0, L1 and so on). On the other hand, if Li score is < 1.0, then + // we'll increase the priority of Li-1. + // Overall what this means is, if the bottom level is already overflowing, then de-prioritize + // compaction of the above level. If the bottom level is not full, then increase the priority of + // above level. + var prevLevel int + for level := t.baseLevel; level < len(s.levels); level++ { + if prios[prevLevel].adjusted >= 1 { + // Avoid absurdly large scores by placing a floor on the score that we'll + // adjust a level by. The value of 0.01 was chosen somewhat arbitrarily + const minScore = 0.01 + if prios[level].score >= minScore { + prios[prevLevel].adjusted /= prios[level].adjusted + } else { + prios[prevLevel].adjusted /= minScore + } + } + prevLevel = level + } + + // Pick all the levels whose original score is >= 1.0, irrespective of their adjusted score. + // We'll still sort them by their adjusted score below. Having both these scores allows us to + // make better decisions about compacting L0. If we see a score >= 1.0, we can do L0->L0 + // compactions. If the adjusted score >= 1.0, then we can do L0->Lbase compactions. + out := prios[:0] + for _, p := range prios[:len(prios)-1] { + if p.score >= 1.0 { + out = append(out, p) + } + } + prios = out + + // Sort by the adjusted score. + sort.Slice(prios, func(i, j int) bool { + return prios[i].adjusted > prios[j].adjusted + }) + return prios +} + +// checkOverlap checks if the given tables overlap with any level from the given "lev" onwards. +func (s *levelsController) checkOverlap(tables []*table.Table, lev int) bool { + kr := getKeyRange(tables...) + for i, lh := range s.levels { + if i < lev { // Skip upper levels. + continue + } + lh.RLock() + left, right := lh.overlappingTables(levelHandlerRLocked{}, kr) + lh.RUnlock() + if right-left > 0 { + return true + } + } + return false +} + +// subcompact runs a single sub-compaction, iterating over the specified key-range only. +// +// We use splits to do a single compaction concurrently. If we have >= 3 tables +// involved in the bottom level during compaction, we choose key ranges to +// split the main compaction up into sub-compactions. Each sub-compaction runs +// concurrently, only iterating over the provided key range, generating tables. +// This speeds up the compaction significantly. +func (s *levelsController) subcompact(it y.Iterator, kr keyRange, cd compactDef, + inflightBuilders *y.Throttle, res chan<- *table.Table) { + + // Check overlap of the top level with the levels which are not being + // compacted in this compaction. + hasOverlap := s.checkOverlap(cd.allTables(), cd.nextLevel.level+1) + + // Pick a discard ts, so we can discard versions below this ts. We should + // never discard any versions starting from above this timestamp, because + // that would affect the snapshot view guarantee provided by transactions. + discardTs := s.kv.orc.discardAtOrBelow() + + // Try to collect stats so that we can inform value log about GC. That would help us find which + // value log file should be GCed. + discardStats := make(map[uint32]int64) + updateStats := func(vs y.ValueStruct) { + // We don't need to store/update discard stats when badger is running in Disk-less mode. + if s.kv.opt.InMemory { + return + } + if vs.Meta&bitValuePointer > 0 { + var vp valuePointer + vp.Decode(vs.Value) + discardStats[vp.Fid] += int64(vp.Len) + } + } + + // exceedsAllowedOverlap returns true if the given key range would overlap with more than 10 + // tables from level below nextLevel (nextLevel+1). This helps avoid generating tables at Li + // with huge overlaps with Li+1. + exceedsAllowedOverlap := func(kr keyRange) bool { + n2n := cd.nextLevel.level + 1 + if n2n <= 1 || n2n >= len(s.levels) { + return false + } + n2nl := s.levels[n2n] + n2nl.RLock() + defer n2nl.RUnlock() + + l, r := n2nl.overlappingTables(levelHandlerRLocked{}, kr) + return r-l >= 10 + } + + var ( + lastKey, skipKey []byte + numBuilds, numVersions int + // Denotes if the first key is a series of duplicate keys had + // "DiscardEarlierVersions" set + firstKeyHasDiscardSet bool + ) + + addKeys := func(builder *table.Builder) { + timeStart := time.Now() + var numKeys, numSkips uint64 + var rangeCheck int + var tableKr keyRange + for ; it.Valid(); it.Next() { + // See if we need to skip the prefix. + if len(cd.dropPrefixes) > 0 && hasAnyPrefixes(it.Key(), cd.dropPrefixes) { + numSkips++ + updateStats(it.Value()) + continue + } + + // See if we need to skip this key. + if len(skipKey) > 0 { + if y.SameKey(it.Key(), skipKey) { + numSkips++ + updateStats(it.Value()) + continue + } else { + skipKey = skipKey[:0] + } + } + + if !y.SameKey(it.Key(), lastKey) { + firstKeyHasDiscardSet = false + if len(kr.right) > 0 && y.CompareKeys(it.Key(), kr.right) >= 0 { + break + } + if builder.ReachedCapacity() { + // Only break if we are on a different key, and have reached capacity. We want + // to ensure that all versions of the key are stored in the same sstable, and + // not divided across multiple tables at the same level. + break + } + lastKey = y.SafeCopy(lastKey, it.Key()) + numVersions = 0 + firstKeyHasDiscardSet = it.Value().Meta&bitDiscardEarlierVersions > 0 + + if len(tableKr.left) == 0 { + tableKr.left = y.SafeCopy(tableKr.left, it.Key()) + } + tableKr.right = lastKey + + rangeCheck++ + if rangeCheck%5000 == 0 { + // This table's range exceeds the allowed range overlap with the level after + // next. So, we stop writing to this table. If we don't do this, then we end up + // doing very expensive compactions involving too many tables. To amortize the + // cost of this check, we do it only every N keys. + if exceedsAllowedOverlap(tableKr) { + // s.kv.opt.Debugf("L%d -> L%d Breaking due to exceedsAllowedOverlap with + // kr: %s\n", cd.thisLevel.level, cd.nextLevel.level, tableKr) + break + } + } + } + + vs := it.Value() + version := y.ParseTs(it.Key()) + + isExpired := isDeletedOrExpired(vs.Meta, vs.ExpiresAt) + + // Do not discard entries inserted by merge operator. These entries will be + // discarded once they're merged + if version <= discardTs && vs.Meta&bitMergeEntry == 0 { + // Keep track of the number of versions encountered for this key. Only consider the + // versions which are below the minReadTs, otherwise, we might end up discarding the + // only valid version for a running transaction. + numVersions++ + // Keep the current version and discard all the next versions if + // - The `discardEarlierVersions` bit is set OR + // - We've already processed `NumVersionsToKeep` number of versions + // (including the current item being processed) + lastValidVersion := vs.Meta&bitDiscardEarlierVersions > 0 || + numVersions == s.kv.opt.NumVersionsToKeep + + if isExpired || lastValidVersion { + // If this version of the key is deleted or expired, skip all the rest of the + // versions. Ensure that we're only removing versions below readTs. + skipKey = y.SafeCopy(skipKey, it.Key()) + + switch { + // Add the key to the table only if it has not expired. + // We don't want to add the deleted/expired keys. + case !isExpired && lastValidVersion: + // Add this key. We have set skipKey, so the following key versions + // would be skipped. + case hasOverlap: + // If this key range has overlap with lower levels, then keep the deletion + // marker with the latest version, discarding the rest. We have set skipKey, + // so the following key versions would be skipped. + default: + // If no overlap, we can skip all the versions, by continuing here. + numSkips++ + updateStats(vs) + continue // Skip adding this key. + } + } + } + numKeys++ + var vp valuePointer + if vs.Meta&bitValuePointer > 0 { + vp.Decode(vs.Value) + } + switch { + case firstKeyHasDiscardSet: + // This key is same as the last key which had "DiscardEarlierVersions" set. The + // the next compactions will drop this key if its ts > + // discardTs (of the next compaction). + builder.AddStaleKey(it.Key(), vs, vp.Len) + case isExpired: + // If the key is expired, the next compaction will drop it if + // its ts > discardTs (of the next compaction). + builder.AddStaleKey(it.Key(), vs, vp.Len) + default: + builder.Add(it.Key(), vs, vp.Len) + } + } + s.kv.opt.Debugf("[%d] LOG Compact. Added %d keys. Skipped %d keys. Iteration took: %v", + cd.compactorId, numKeys, numSkips, time.Since(timeStart).Round(time.Millisecond)) + } // End of function: addKeys + + if len(kr.left) > 0 { + it.Seek(kr.left) + } else { + it.Rewind() + } + for it.Valid() { + if len(kr.right) > 0 && y.CompareKeys(it.Key(), kr.right) >= 0 { + break + } + + bopts := buildTableOptions(s.kv) + // Set TableSize to the target file size for that level. + bopts.TableSize = uint64(cd.t.fileSz[cd.nextLevel.level]) + builder := table.NewTableBuilder(bopts) + + // This would do the iteration and add keys to builder. + addKeys(builder) + + // It was true that it.Valid() at least once in the loop above, which means we + // called Add() at least once, and builder is not Empty(). + if builder.Empty() { + // Cleanup builder resources: + builder.Finish() + builder.Close() + continue + } + numBuilds++ + if err := inflightBuilders.Do(); err != nil { + // Can't return from here, until I decrRef all the tables that I built so far. + break + } + go func(builder *table.Builder, fileID uint64) { + var err error + defer inflightBuilders.Done(err) + defer builder.Close() + + var tbl *table.Table + if s.kv.opt.InMemory { + tbl, err = table.OpenInMemoryTable(builder.Finish(), fileID, &bopts) + } else { + fname := table.NewFilename(fileID, s.kv.opt.Dir) + tbl, err = table.CreateTable(fname, builder) + } + + // If we couldn't build the table, return fast. + if err != nil { + return + } + res <- tbl + }(builder, s.reserveFileID()) + } + s.kv.vlog.updateDiscardStats(discardStats) + s.kv.opt.Debugf("Discard stats: %v", discardStats) +} + +// compactBuildTables merges topTables and botTables to form a list of new tables. +func (s *levelsController) compactBuildTables( + lev int, cd compactDef) ([]*table.Table, func() error, error) { + + topTables := cd.top + botTables := cd.bot + + numTables := int64(len(topTables) + len(botTables)) + y.NumCompactionTablesAdd(s.kv.opt.MetricsEnabled, numTables) + defer y.NumCompactionTablesAdd(s.kv.opt.MetricsEnabled, -numTables) + + cd.span.Annotatef(nil, "Top tables count: %v Bottom tables count: %v", + len(topTables), len(botTables)) + + keepTable := func(t *table.Table) bool { + for _, prefix := range cd.dropPrefixes { + if bytes.HasPrefix(t.Smallest(), prefix) && + bytes.HasPrefix(t.Biggest(), prefix) { + // All the keys in this table have the dropPrefix. So, this + // table does not need to be in the iterator and can be + // dropped immediately. + return false + } + } + return true + } + var valid []*table.Table + for _, table := range botTables { + if keepTable(table) { + valid = append(valid, table) + } + } + + newIterator := func() []y.Iterator { + // Create iterators across all the tables involved first. + var iters []y.Iterator + switch { + case lev == 0: + iters = appendIteratorsReversed(iters, topTables, table.NOCACHE) + case len(topTables) > 0: + y.AssertTrue(len(topTables) == 1) + iters = []y.Iterator{topTables[0].NewIterator(table.NOCACHE)} + } + // Next level has level>=1 and we can use ConcatIterator as key ranges do not overlap. + return append(iters, table.NewConcatIterator(valid, table.NOCACHE)) + } + + res := make(chan *table.Table, 3) + inflightBuilders := y.NewThrottle(8 + len(cd.splits)) + for _, kr := range cd.splits { + // Initiate Do here so we can register the goroutines for buildTables too. + if err := inflightBuilders.Do(); err != nil { + s.kv.opt.Errorf("cannot start subcompaction: %+v", err) + return nil, nil, err + } + go func(kr keyRange) { + defer inflightBuilders.Done(nil) + it := table.NewMergeIterator(newIterator(), false) + defer it.Close() + s.subcompact(it, kr, cd, inflightBuilders, res) + }(kr) + } + + var newTables []*table.Table + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + for t := range res { + newTables = append(newTables, t) + } + }() + + // Wait for all table builders to finish and also for newTables accumulator to finish. + err := inflightBuilders.Finish() + close(res) + wg.Wait() // Wait for all tables to be picked up. + + if err == nil { + // Ensure created files' directory entries are visible. We don't mind the extra latency + // from not doing this ASAP after all file creation has finished because this is a + // background operation. + err = s.kv.syncDir(s.kv.opt.Dir) + } + + if err != nil { + // An error happened. Delete all the newly created table files (by calling DecrRef + // -- we're the only holders of a ref). + _ = decrRefs(newTables) + return nil, nil, y.Wrapf(err, "while running compactions for: %+v", cd) + } + + sort.Slice(newTables, func(i, j int) bool { + return y.CompareKeys(newTables[i].Biggest(), newTables[j].Biggest()) < 0 + }) + return newTables, func() error { return decrRefs(newTables) }, nil +} + +func buildChangeSet(cd *compactDef, newTables []*table.Table) pb.ManifestChangeSet { + changes := []*pb.ManifestChange{} + for _, table := range newTables { + changes = append(changes, + newCreateChange(table.ID(), cd.nextLevel.level, table.KeyID(), table.CompressionType())) + } + for _, table := range cd.top { + // Add a delete change only if the table is not in memory. + if !table.IsInmemory { + changes = append(changes, newDeleteChange(table.ID())) + } + } + for _, table := range cd.bot { + changes = append(changes, newDeleteChange(table.ID())) + } + return pb.ManifestChangeSet{Changes: changes} +} + +func hasAnyPrefixes(s []byte, listOfPrefixes [][]byte) bool { + for _, prefix := range listOfPrefixes { + if bytes.HasPrefix(s, prefix) { + return true + } + } + + return false +} + +func containsPrefix(table *table.Table, prefix []byte) bool { + smallValue := table.Smallest() + largeValue := table.Biggest() + if bytes.HasPrefix(smallValue, prefix) { + return true + } + if bytes.HasPrefix(largeValue, prefix) { + return true + } + isPresent := func() bool { + ti := table.NewIterator(0) + defer ti.Close() + // In table iterator's Seek, we assume that key has version in last 8 bytes. We set + // version=0 (ts=math.MaxUint64), so that we don't skip the key prefixed with prefix. + ti.Seek(y.KeyWithTs(prefix, math.MaxUint64)) + if bytes.HasPrefix(ti.Key(), prefix) { + return true + } + return false + } + + if bytes.Compare(prefix, smallValue) > 0 && + bytes.Compare(prefix, largeValue) < 0 { + // There may be a case when table contains [0x0000,...., 0xffff]. If we are searching for + // k=0x0011, we should not directly infer that k is present. It may not be present. + if !isPresent() { + return false + } + return true + } + + return false +} + +func containsAnyPrefixes(table *table.Table, listOfPrefixes [][]byte) bool { + for _, prefix := range listOfPrefixes { + if containsPrefix(table, prefix) { + return true + } + } + + return false +} + +type compactDef struct { + span *otrace.Span + + compactorId int + t targets + p compactionPriority + thisLevel *levelHandler + nextLevel *levelHandler + + top []*table.Table + bot []*table.Table + + thisRange keyRange + nextRange keyRange + splits []keyRange + + thisSize int64 + + dropPrefixes [][]byte +} + +// addSplits can allow us to run multiple sub-compactions in parallel across the split key ranges. +func (s *levelsController) addSplits(cd *compactDef) { + cd.splits = cd.splits[:0] + + // Let's say we have 10 tables in cd.bot and min width = 3. Then, we'll pick + // 0, 1, 2 (pick), 3, 4, 5 (pick), 6, 7, 8 (pick), 9 (pick, because last table). + // This gives us 4 picks for 10 tables. + // In an edge case, 142 tables in bottom led to 48 splits. That's too many splits, because it + // then uses up a lot of memory for table builder. + // We should keep it so we have at max 5 splits. + width := int(math.Ceil(float64(len(cd.bot)) / 5.0)) + if width < 3 { + width = 3 + } + skr := cd.thisRange + skr.extend(cd.nextRange) + + addRange := func(right []byte) { + skr.right = y.Copy(right) + cd.splits = append(cd.splits, skr) + + skr.left = skr.right + } + + for i, t := range cd.bot { + // last entry in bottom table. + if i == len(cd.bot)-1 { + addRange([]byte{}) + return + } + if i%width == width-1 { + // Right should always have ts=maxUint64 otherwise we'll lose keys + // in subcompaction. Consider the following. + // Top table is [A1...C3(deleted)] + // bot table is [B1....C2] + // This will generate splits like [A1 ... C2] . Notice that we + // dropped the C3 which is the last key of the top table. + // See TestCompaction/with_split test. + right := y.KeyWithTs(y.ParseKey(t.Biggest()), math.MaxUint64) + addRange(right) + } + } +} + +func (cd *compactDef) lockLevels() { + cd.thisLevel.RLock() + cd.nextLevel.RLock() +} + +func (cd *compactDef) unlockLevels() { + cd.nextLevel.RUnlock() + cd.thisLevel.RUnlock() +} + +func (cd *compactDef) allTables() []*table.Table { + ret := make([]*table.Table, 0, len(cd.top)+len(cd.bot)) + ret = append(ret, cd.top...) + ret = append(ret, cd.bot...) + return ret +} + +func (s *levelsController) fillTablesL0ToL0(cd *compactDef) bool { + if cd.compactorId != 0 { + // Only compactor zero can work on this. + return false + } + + cd.nextLevel = s.levels[0] + cd.nextRange = keyRange{} + cd.bot = nil + + // Because this level and next level are both level 0, we should NOT acquire + // the read lock twice, because it can result in a deadlock. So, we don't + // call compactDef.lockLevels, instead locking the level only once and + // directly here. + // + // As per godocs on RWMutex: + // If a goroutine holds a RWMutex for reading and another goroutine might + // call Lock, no goroutine should expect to be able to acquire a read lock + // until the initial read lock is released. In particular, this prohibits + // recursive read locking. This is to ensure that the lock eventually + // becomes available; a blocked Lock call excludes new readers from + // acquiring the lock. + y.AssertTrue(cd.thisLevel.level == 0) + y.AssertTrue(cd.nextLevel.level == 0) + s.levels[0].RLock() + defer s.levels[0].RUnlock() + + s.cstatus.Lock() + defer s.cstatus.Unlock() + + top := cd.thisLevel.tables + var out []*table.Table + now := time.Now() + for _, t := range top { + if t.Size() >= 2*cd.t.fileSz[0] { + // This file is already big, don't include it. + continue + } + if now.Sub(t.CreatedAt) < 10*time.Second { + // Just created it 10s ago. Don't pick for compaction. + continue + } + if _, beingCompacted := s.cstatus.tables[t.ID()]; beingCompacted { + continue + } + out = append(out, t) + } + + if len(out) < 4 { + // If we don't have enough tables to merge in L0, don't do it. + return false + } + cd.thisRange = infRange + cd.top = out + + // Avoid any other L0 -> Lbase from happening, while this is going on. + thisLevel := s.cstatus.levels[cd.thisLevel.level] + thisLevel.ranges = append(thisLevel.ranges, infRange) + for _, t := range out { + s.cstatus.tables[t.ID()] = struct{}{} + } + + // For L0->L0 compaction, we set the target file size to max, so the output is always one file. + // This significantly decreases the L0 table stalls and improves the performance. + cd.t.fileSz[0] = math.MaxUint32 + return true +} + +func (s *levelsController) fillTablesL0ToLbase(cd *compactDef) bool { + if cd.nextLevel.level == 0 { + panic("Base level can't be zero.") + } + // We keep cd.p.adjusted > 0.0 here to allow functions in db.go to artificially trigger + // L0->Lbase compactions. Those functions wouldn't be setting the adjusted score. + if cd.p.adjusted > 0.0 && cd.p.adjusted < 1.0 { + // Do not compact to Lbase if adjusted score is less than 1.0. + return false + } + cd.lockLevels() + defer cd.unlockLevels() + + top := cd.thisLevel.tables + if len(top) == 0 { + return false + } + + var out []*table.Table + if len(cd.dropPrefixes) > 0 { + // Use all tables if drop prefix is set. We don't want to compact only a + // sub-range. We want to compact all the tables. + out = top + + } else { + var kr keyRange + // cd.top[0] is the oldest file. So we start from the oldest file first. + for _, t := range top { + dkr := getKeyRange(t) + if kr.overlapsWith(dkr) { + out = append(out, t) + kr.extend(dkr) + } else { + break + } + } + } + cd.thisRange = getKeyRange(out...) + cd.top = out + + left, right := cd.nextLevel.overlappingTables(levelHandlerRLocked{}, cd.thisRange) + cd.bot = make([]*table.Table, right-left) + copy(cd.bot, cd.nextLevel.tables[left:right]) + + if len(cd.bot) == 0 { + cd.nextRange = cd.thisRange + } else { + cd.nextRange = getKeyRange(cd.bot...) + } + return s.cstatus.compareAndAdd(thisAndNextLevelRLocked{}, *cd) +} + +// fillTablesL0 would try to fill tables from L0 to be compacted with Lbase. If +// it can not do that, it would try to compact tables from L0 -> L0. +// +// Say L0 has 10 tables. +// fillTablesL0ToLbase picks up 5 tables to compact from L0 -> L5. +// Next call to fillTablesL0 would run L0ToLbase again, which fails this time. +// So, instead, we run fillTablesL0ToL0, which picks up rest of the 5 tables to +// be compacted within L0. Additionally, it would set the compaction range in +// cstatus to inf, so no other L0 -> Lbase compactions can happen. +// Thus, L0 -> L0 must finish for the next L0 -> Lbase to begin. +func (s *levelsController) fillTablesL0(cd *compactDef) bool { + if ok := s.fillTablesL0ToLbase(cd); ok { + return true + } + return s.fillTablesL0ToL0(cd) +} + +// sortByStaleData sorts tables based on the amount of stale data they have. +// This is useful in removing tombstones. +func (s *levelsController) sortByStaleDataSize(tables []*table.Table, cd *compactDef) { + if len(tables) == 0 || cd.nextLevel == nil { + return + } + + sort.Slice(tables, func(i, j int) bool { + return tables[i].StaleDataSize() > tables[j].StaleDataSize() + }) +} + +// sortByHeuristic sorts tables in increasing order of MaxVersion, so we +// compact older tables first. +func (s *levelsController) sortByHeuristic(tables []*table.Table, cd *compactDef) { + if len(tables) == 0 || cd.nextLevel == nil { + return + } + + // Sort tables by max version. This is what RocksDB does. + sort.Slice(tables, func(i, j int) bool { + return tables[i].MaxVersion() < tables[j].MaxVersion() + }) +} + +// This function should be called with lock on levels. +func (s *levelsController) fillMaxLevelTables(tables []*table.Table, cd *compactDef) bool { + sortedTables := make([]*table.Table, len(tables)) + copy(sortedTables, tables) + s.sortByStaleDataSize(sortedTables, cd) + + if len(sortedTables) > 0 && sortedTables[0].StaleDataSize() == 0 { + // This is a maxLevel to maxLevel compaction and we don't have any stale data. + return false + } + cd.bot = []*table.Table{} + collectBotTables := func(t *table.Table, needSz int64) { + totalSize := t.Size() + + j := sort.Search(len(tables), func(i int) bool { + return y.CompareKeys(tables[i].Smallest(), t.Smallest()) >= 0 + }) + y.AssertTrue(tables[j].ID() == t.ID()) + j++ + // Collect tables until we reach the the required size. + for j < len(tables) { + newT := tables[j] + totalSize += newT.Size() + + if totalSize >= needSz { + break + } + cd.bot = append(cd.bot, newT) + cd.nextRange.extend(getKeyRange(newT)) + j++ + } + } + now := time.Now() + for _, t := range sortedTables { + // If the maxVersion is above the discardTs, we won't clean anything in + // the compaction. So skip this table. + if t.MaxVersion() > s.kv.orc.discardAtOrBelow() { + continue + } + if now.Sub(t.CreatedAt) < time.Hour { + // Just created it an hour ago. Don't pick for compaction. + continue + } + // If the stale data size is less than 10 MB, it might not be worth + // rewriting the table. Skip it. + if t.StaleDataSize() < 10<<20 { + continue + } + + cd.thisSize = t.Size() + cd.thisRange = getKeyRange(t) + // Set the next range as the same as the current range. If we don't do + // this, we won't be able to run more than one max level compactions. + cd.nextRange = cd.thisRange + // If we're already compacting this range, don't do anything. + if s.cstatus.overlapsWith(cd.thisLevel.level, cd.thisRange) { + continue + } + + // Found a valid table! + cd.top = []*table.Table{t} + + needFileSz := cd.t.fileSz[cd.thisLevel.level] + // The table size is what we want so no need to collect more tables. + if t.Size() >= needFileSz { + break + } + // TableSize is less than what we want. Collect more tables for compaction. + // If the level has multiple small tables, we collect all of them + // together to form a bigger table. + collectBotTables(t, needFileSz) + if !s.cstatus.compareAndAdd(thisAndNextLevelRLocked{}, *cd) { + cd.bot = cd.bot[:0] + cd.nextRange = keyRange{} + continue + } + return true + } + if len(cd.top) == 0 { + return false + } + + return s.cstatus.compareAndAdd(thisAndNextLevelRLocked{}, *cd) +} + +func (s *levelsController) fillTables(cd *compactDef) bool { + cd.lockLevels() + defer cd.unlockLevels() + + tables := make([]*table.Table, len(cd.thisLevel.tables)) + copy(tables, cd.thisLevel.tables) + if len(tables) == 0 { + return false + } + // We're doing a maxLevel to maxLevel compaction. Pick tables based on the stale data size. + if cd.thisLevel.isLastLevel() { + return s.fillMaxLevelTables(tables, cd) + } + // We pick tables, so we compact older tables first. This is similar to + // kOldestLargestSeqFirst in RocksDB. + s.sortByHeuristic(tables, cd) + + for _, t := range tables { + cd.thisSize = t.Size() + cd.thisRange = getKeyRange(t) + // If we're already compacting this range, don't do anything. + if s.cstatus.overlapsWith(cd.thisLevel.level, cd.thisRange) { + continue + } + cd.top = []*table.Table{t} + left, right := cd.nextLevel.overlappingTables(levelHandlerRLocked{}, cd.thisRange) + + cd.bot = make([]*table.Table, right-left) + copy(cd.bot, cd.nextLevel.tables[left:right]) + + if len(cd.bot) == 0 { + cd.bot = []*table.Table{} + cd.nextRange = cd.thisRange + if !s.cstatus.compareAndAdd(thisAndNextLevelRLocked{}, *cd) { + continue + } + return true + } + cd.nextRange = getKeyRange(cd.bot...) + + if s.cstatus.overlapsWith(cd.nextLevel.level, cd.nextRange) { + continue + } + if !s.cstatus.compareAndAdd(thisAndNextLevelRLocked{}, *cd) { + continue + } + return true + } + return false +} + +func (s *levelsController) runCompactDef(id, l int, cd compactDef) (err error) { + if len(cd.t.fileSz) == 0 { + return errors.New("Filesizes cannot be zero. Targets are not set") + } + timeStart := time.Now() + + thisLevel := cd.thisLevel + nextLevel := cd.nextLevel + + y.AssertTrue(len(cd.splits) == 0) + if thisLevel.level == nextLevel.level { + // don't do anything for L0 -> L0 and Lmax -> Lmax. + } else { + s.addSplits(&cd) + } + if len(cd.splits) == 0 { + cd.splits = append(cd.splits, keyRange{}) + } + + // Table should never be moved directly between levels, always be rewritten to allow discarding + // invalid versions. + + newTables, decr, err := s.compactBuildTables(l, cd) + if err != nil { + return err + } + defer func() { + // Only assign to err, if it's not already nil. + if decErr := decr(); err == nil { + err = decErr + } + }() + changeSet := buildChangeSet(&cd, newTables) + + // We write to the manifest _before_ we delete files (and after we created files) + if err := s.kv.manifest.addChanges(changeSet.Changes); err != nil { + return err + } + + // See comment earlier in this function about the ordering of these ops, and the order in which + // we access levels when reading. + if err := nextLevel.replaceTables(cd.bot, newTables); err != nil { + return err + } + if err := thisLevel.deleteTables(cd.top); err != nil { + return err + } + + // Note: For level 0, while doCompact is running, it is possible that new tables are added. + // However, the tables are added only to the end, so it is ok to just delete the first table. + + from := append(tablesToString(cd.top), tablesToString(cd.bot)...) + to := tablesToString(newTables) + if dur := time.Since(timeStart); dur > 2*time.Second { + var expensive string + if dur > time.Second { + expensive = " [E]" + } + s.kv.opt.Infof("[%d]%s LOG Compact %d->%d (%d, %d -> %d tables with %d splits)."+ + " [%s] -> [%s], took %v\n", + id, expensive, thisLevel.level, nextLevel.level, len(cd.top), len(cd.bot), + len(newTables), len(cd.splits), strings.Join(from, " "), strings.Join(to, " "), + dur.Round(time.Millisecond)) + } + + if cd.thisLevel.level != 0 && len(newTables) > 2*s.kv.opt.LevelSizeMultiplier { + s.kv.opt.Debugf("This Range (numTables: %d)\nLeft:\n%s\nRight:\n%s\n", + len(cd.top), hex.Dump(cd.thisRange.left), hex.Dump(cd.thisRange.right)) + s.kv.opt.Debugf("Next Range (numTables: %d)\nLeft:\n%s\nRight:\n%s\n", + len(cd.bot), hex.Dump(cd.nextRange.left), hex.Dump(cd.nextRange.right)) + } + return nil +} + +func tablesToString(tables []*table.Table) []string { + var res []string + for _, t := range tables { + res = append(res, fmt.Sprintf("%05d", t.ID())) + } + res = append(res, ".") + return res +} + +var errFillTables = errors.New("Unable to fill tables") + +// doCompact picks some table on level l and compacts it away to the next level. +func (s *levelsController) doCompact(id int, p compactionPriority) error { + l := p.level + y.AssertTrue(l < s.kv.opt.MaxLevels) // Sanity check. + if p.t.baseLevel == 0 { + p.t = s.levelTargets() + } + + _, span := otrace.StartSpan(context.Background(), "Badger.Compaction") + defer span.End() + + cd := compactDef{ + compactorId: id, + span: span, + p: p, + t: p.t, + thisLevel: s.levels[l], + dropPrefixes: p.dropPrefixes, + } + + // While picking tables to be compacted, both levels' tables are expected to + // remain unchanged. + if l == 0 { + cd.nextLevel = s.levels[p.t.baseLevel] + if !s.fillTablesL0(&cd) { + return errFillTables + } + } else { + cd.nextLevel = cd.thisLevel + // We're not compacting the last level so pick the next level. + if !cd.thisLevel.isLastLevel() { + cd.nextLevel = s.levels[l+1] + } + if !s.fillTables(&cd) { + return errFillTables + } + } + defer s.cstatus.delete(cd) // Remove the ranges from compaction status. + + span.Annotatef(nil, "Compaction: %+v", cd) + if err := s.runCompactDef(id, l, cd); err != nil { + // This compaction couldn't be done successfully. + s.kv.opt.Warningf("[Compactor: %d] LOG Compact FAILED with error: %+v: %+v", id, err, cd) + return err + } + + s.kv.opt.Debugf("[Compactor: %d] Compaction for level: %d DONE", id, cd.thisLevel.level) + return nil +} + +func (s *levelsController) addLevel0Table(t *table.Table) error { + // Add table to manifest file only if it is not opened in memory. We don't want to add a table + // to the manifest file if it exists only in memory. + if !t.IsInmemory { + // We update the manifest _before_ the table becomes part of a levelHandler, because at that + // point it could get used in some compaction. This ensures the manifest file gets updated in + // the proper order. (That means this update happens before that of some compaction which + // deletes the table.) + err := s.kv.manifest.addChanges([]*pb.ManifestChange{ + newCreateChange(t.ID(), 0, t.KeyID(), t.CompressionType()), + }) + if err != nil { + return err + } + } + + for !s.levels[0].tryAddLevel0Table(t) { + // Before we unstall, we need to make sure that level 0 is healthy. + timeStart := time.Now() + for s.levels[0].numTables() >= s.kv.opt.NumLevelZeroTablesStall { + time.Sleep(10 * time.Millisecond) + } + dur := time.Since(timeStart) + if dur > time.Second { + s.kv.opt.Infof("L0 was stalled for %s\n", dur.Round(time.Millisecond)) + } + atomic.AddInt64(&s.l0stallsMs, int64(dur.Round(time.Millisecond))) + } + + return nil +} + +func (s *levelsController) close() error { + err := s.cleanupLevels() + return y.Wrap(err, "levelsController.Close") +} + +// get searches for a given key in all the levels of the LSM tree. It returns +// key version <= the expected version (maxVs). If not found, it returns an empty +// y.ValueStruct. +func (s *levelsController) get(key []byte, maxVs y.ValueStruct, startLevel int) ( + y.ValueStruct, error) { + if s.kv.IsClosed() { + return y.ValueStruct{}, ErrDBClosed + } + // It's important that we iterate the levels from 0 on upward. The reason is, if we iterated + // in opposite order, or in parallel (naively calling all the h.RLock() in some order) we could + // read level L's tables post-compaction and level L+1's tables pre-compaction. (If we do + // parallelize this, we will need to call the h.RLock() function by increasing order of level + // number.) + version := y.ParseTs(key) + for _, h := range s.levels { + // Ignore all levels below startLevel. This is useful for GC when L0 is kept in memory. + if h.level < startLevel { + continue + } + vs, err := h.get(key) // Calls h.RLock() and h.RUnlock(). + if err != nil { + return y.ValueStruct{}, y.Wrapf(err, "get key: %q", key) + } + if vs.Value == nil && vs.Meta == 0 { + continue + } + if vs.Version == version { + return vs, nil + } + if maxVs.Version < vs.Version { + maxVs = vs + } + } + return maxVs, nil +} + +func appendIteratorsReversed(out []y.Iterator, th []*table.Table, opt int) []y.Iterator { + for i := len(th) - 1; i >= 0; i-- { + // This will increment the reference of the table handler. + out = append(out, th[i].NewIterator(opt)) + } + return out +} + +// appendIterators appends iterators to an array of iterators, for merging. +// Note: This obtains references for the table handlers. Remember to close these iterators. +func (s *levelsController) appendIterators( + iters []y.Iterator, opt *IteratorOptions) []y.Iterator { + // Just like with get, it's important we iterate the levels from 0 on upward, to avoid missing + // data when there's a compaction. + for _, level := range s.levels { + iters = level.appendIterators(iters, opt) + } + return iters +} + +// TableInfo represents the information about a table. +type TableInfo struct { + ID uint64 + Level int + Left []byte + Right []byte + KeyCount uint32 // Number of keys in the table + OnDiskSize uint32 + StaleDataSize uint32 + UncompressedSize uint32 + MaxVersion uint64 + IndexSz int + BloomFilterSize int +} + +func (s *levelsController) getTableInfo() (result []TableInfo) { + for _, l := range s.levels { + l.RLock() + for _, t := range l.tables { + info := TableInfo{ + ID: t.ID(), + Level: l.level, + Left: t.Smallest(), + Right: t.Biggest(), + KeyCount: t.KeyCount(), + OnDiskSize: t.OnDiskSize(), + StaleDataSize: t.StaleDataSize(), + IndexSz: t.IndexSize(), + BloomFilterSize: t.BloomFilterSize(), + UncompressedSize: t.UncompressedSize(), + MaxVersion: t.MaxVersion(), + } + result = append(result, info) + } + l.RUnlock() + } + sort.Slice(result, func(i, j int) bool { + if result[i].Level != result[j].Level { + return result[i].Level < result[j].Level + } + return result[i].ID < result[j].ID + }) + return +} + +type LevelInfo struct { + Level int + NumTables int + Size int64 + TargetSize int64 + TargetFileSize int64 + IsBaseLevel bool + Score float64 + Adjusted float64 + StaleDatSize int64 +} + +func (s *levelsController) getLevelInfo() []LevelInfo { + t := s.levelTargets() + prios := s.pickCompactLevels() + result := make([]LevelInfo, len(s.levels)) + for i, l := range s.levels { + l.RLock() + result[i].Level = i + result[i].Size = l.totalSize + result[i].NumTables = len(l.tables) + result[i].StaleDatSize = l.totalStaleSize + + l.RUnlock() + + result[i].TargetSize = t.targetSz[i] + result[i].TargetFileSize = t.fileSz[i] + result[i].IsBaseLevel = t.baseLevel == i + } + for _, p := range prios { + result[p.level].Score = p.score + result[p.level].Adjusted = p.adjusted + } + return result +} + +// verifyChecksum verifies checksum for all tables on all levels. +func (s *levelsController) verifyChecksum() error { + var tables []*table.Table + for _, l := range s.levels { + l.RLock() + tables = tables[:0] + for _, t := range l.tables { + tables = append(tables, t) + t.IncrRef() + } + l.RUnlock() + + for _, t := range tables { + errChkVerify := t.VerifyChecksum() + if err := t.DecrRef(); err != nil { + s.kv.opt.Errorf("unable to decrease reference of table: %s while "+ + "verifying checksum with error: %s", t.Filename(), err) + } + + if errChkVerify != nil { + return errChkVerify + } + } + } + + return nil +} + +// Returns the sorted list of splits for all the levels and tables based +// on the block offsets. +func (s *levelsController) keySplits(numPerTable int, prefix []byte) []string { + splits := make([]string, 0) + for _, l := range s.levels { + l.RLock() + for _, t := range l.tables { + tableSplits := t.KeySplits(numPerTable, prefix) + splits = append(splits, tableSplits...) + } + l.RUnlock() + } + sort.Strings(splits) + return splits +} diff --git a/vendor/github.com/dgraph-io/badger/v3/logger.go b/vendor/github.com/dgraph-io/badger/v3/logger.go new file mode 100644 index 0000000000..c7b4cd6c1f --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/logger.go @@ -0,0 +1,105 @@ +/* + * Copyright 2018 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "log" + "os" +) + +// Logger is implemented by any logging system that is used for standard logs. +type Logger interface { + Errorf(string, ...interface{}) + Warningf(string, ...interface{}) + Infof(string, ...interface{}) + Debugf(string, ...interface{}) +} + +// Errorf logs an ERROR log message to the logger specified in opts or to the +// global logger if no logger is specified in opts. +func (opt *Options) Errorf(format string, v ...interface{}) { + if opt.Logger == nil { + return + } + opt.Logger.Errorf(format, v...) +} + +// Infof logs an INFO message to the logger specified in opts. +func (opt *Options) Infof(format string, v ...interface{}) { + if opt.Logger == nil { + return + } + opt.Logger.Infof(format, v...) +} + +// Warningf logs a WARNING message to the logger specified in opts. +func (opt *Options) Warningf(format string, v ...interface{}) { + if opt.Logger == nil { + return + } + opt.Logger.Warningf(format, v...) +} + +// Debugf logs a DEBUG message to the logger specified in opts. +func (opt *Options) Debugf(format string, v ...interface{}) { + if opt.Logger == nil { + return + } + opt.Logger.Debugf(format, v...) +} + +type loggingLevel int + +const ( + DEBUG loggingLevel = iota + INFO + WARNING + ERROR +) + +type defaultLog struct { + *log.Logger + level loggingLevel +} + +func defaultLogger(level loggingLevel) *defaultLog { + return &defaultLog{Logger: log.New(os.Stderr, "badger ", log.LstdFlags), level: level} +} + +func (l *defaultLog) Errorf(f string, v ...interface{}) { + if l.level <= ERROR { + l.Printf("ERROR: "+f, v...) + } +} + +func (l *defaultLog) Warningf(f string, v ...interface{}) { + if l.level <= WARNING { + l.Printf("WARNING: "+f, v...) + } +} + +func (l *defaultLog) Infof(f string, v ...interface{}) { + if l.level <= INFO { + l.Printf("INFO: "+f, v...) + } +} + +func (l *defaultLog) Debugf(f string, v ...interface{}) { + if l.level <= DEBUG { + l.Printf("DEBUG: "+f, v...) + } +} diff --git a/vendor/github.com/dgraph-io/badger/v3/managed_db.go b/vendor/github.com/dgraph-io/badger/v3/managed_db.go new file mode 100644 index 0000000000..23c7988457 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/managed_db.go @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +// OpenManaged returns a new DB, which allows more control over setting +// transaction timestamps, aka managed mode. +// +// This is only useful for databases built on top of Badger (like Dgraph), and +// can be ignored by most users. +func OpenManaged(opts Options) (*DB, error) { + opts.managedTxns = true + return Open(opts) +} + +// NewTransactionAt follows the same logic as DB.NewTransaction(), but uses the +// provided read timestamp. +// +// This is only useful for databases built on top of Badger (like Dgraph), and +// can be ignored by most users. +func (db *DB) NewTransactionAt(readTs uint64, update bool) *Txn { + if !db.opt.managedTxns { + panic("Cannot use NewTransactionAt with managedDB=false. Use NewTransaction instead.") + } + txn := db.newTransaction(update, true) + txn.readTs = readTs + return txn +} + +// NewWriteBatchAt is similar to NewWriteBatch but it allows user to set the commit timestamp. +// NewWriteBatchAt is supposed to be used only in the managed mode. +func (db *DB) NewWriteBatchAt(commitTs uint64) *WriteBatch { + if !db.opt.managedTxns { + panic("cannot use NewWriteBatchAt with managedDB=false. Use NewWriteBatch instead") + } + + wb := db.newWriteBatch(true) + wb.commitTs = commitTs + wb.txn.commitTs = commitTs + return wb +} +func (db *DB) NewManagedWriteBatch() *WriteBatch { + if !db.opt.managedTxns { + panic("cannot use NewManagedWriteBatch with managedDB=false. Use NewWriteBatch instead") + } + + wb := db.newWriteBatch(true) + return wb +} + +// CommitAt commits the transaction, following the same logic as Commit(), but +// at the given commit timestamp. This will panic if not used with managed transactions. +// +// This is only useful for databases built on top of Badger (like Dgraph), and +// can be ignored by most users. +func (txn *Txn) CommitAt(commitTs uint64, callback func(error)) error { + if !txn.db.opt.managedTxns { + panic("Cannot use CommitAt with managedDB=false. Use Commit instead.") + } + txn.commitTs = commitTs + if callback == nil { + return txn.Commit() + } + txn.CommitWith(callback) + return nil +} + +// SetDiscardTs sets a timestamp at or below which, any invalid or deleted +// versions can be discarded from the LSM tree, and thence from the value log to +// reclaim disk space. Can only be used with managed transactions. +func (db *DB) SetDiscardTs(ts uint64) { + if !db.opt.managedTxns { + panic("Cannot use SetDiscardTs with managedDB=false.") + } + db.orc.setDiscardTs(ts) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/manifest.go b/vendor/github.com/dgraph-io/badger/v3/manifest.go new file mode 100644 index 0000000000..bb27fb2477 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/manifest.go @@ -0,0 +1,475 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "hash/crc32" + "io" + "os" + "path/filepath" + "sync" + + "github.com/dgraph-io/badger/v3/options" + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/y" + "github.com/golang/protobuf/proto" + "github.com/pkg/errors" +) + +// Manifest represents the contents of the MANIFEST file in a Badger store. +// +// The MANIFEST file describes the startup state of the db -- all LSM files and what level they're +// at. +// +// It consists of a sequence of ManifestChangeSet objects. Each of these is treated atomically, +// and contains a sequence of ManifestChange's (file creations/deletions) which we use to +// reconstruct the manifest at startup. +type Manifest struct { + Levels []levelManifest + Tables map[uint64]TableManifest + + // Contains total number of creation and deletion changes in the manifest -- used to compute + // whether it'd be useful to rewrite the manifest. + Creations int + Deletions int +} + +func createManifest() Manifest { + levels := make([]levelManifest, 0) + return Manifest{ + Levels: levels, + Tables: make(map[uint64]TableManifest), + } +} + +// levelManifest contains information about LSM tree levels +// in the MANIFEST file. +type levelManifest struct { + Tables map[uint64]struct{} // Set of table id's +} + +// TableManifest contains information about a specific table +// in the LSM tree. +type TableManifest struct { + Level uint8 + KeyID uint64 + Compression options.CompressionType +} + +// manifestFile holds the file pointer (and other info) about the manifest file, which is a log +// file we append to. +type manifestFile struct { + fp *os.File + directory string + // We make this configurable so that unit tests can hit rewrite() code quickly + deletionsRewriteThreshold int + + // Guards appends, which includes access to the manifest field. + appendLock sync.Mutex + + // Used to track the current state of the manifest, used when rewriting. + manifest Manifest + + // Used to indicate if badger was opened in InMemory mode. + inMemory bool +} + +const ( + // ManifestFilename is the filename for the manifest file. + ManifestFilename = "MANIFEST" + manifestRewriteFilename = "MANIFEST-REWRITE" + manifestDeletionsRewriteThreshold = 10000 + manifestDeletionsRatio = 10 +) + +// asChanges returns a sequence of changes that could be used to recreate the Manifest in its +// present state. +func (m *Manifest) asChanges() []*pb.ManifestChange { + changes := make([]*pb.ManifestChange, 0, len(m.Tables)) + for id, tm := range m.Tables { + changes = append(changes, newCreateChange(id, int(tm.Level), tm.KeyID, tm.Compression)) + } + return changes +} + +func (m *Manifest) clone() Manifest { + changeSet := pb.ManifestChangeSet{Changes: m.asChanges()} + ret := createManifest() + y.Check(applyChangeSet(&ret, &changeSet)) + return ret +} + +// openOrCreateManifestFile opens a Badger manifest file if it exists, or creates one if +// doesn’t exists. +func openOrCreateManifestFile(opt Options) ( + ret *manifestFile, result Manifest, err error) { + if opt.InMemory { + return &manifestFile{inMemory: true}, Manifest{}, nil + } + return helpOpenOrCreateManifestFile(opt.Dir, opt.ReadOnly, manifestDeletionsRewriteThreshold) +} + +func helpOpenOrCreateManifestFile(dir string, readOnly bool, deletionsThreshold int) ( + *manifestFile, Manifest, error) { + + path := filepath.Join(dir, ManifestFilename) + var flags y.Flags + if readOnly { + flags |= y.ReadOnly + } + fp, err := y.OpenExistingFile(path, flags) // We explicitly sync in addChanges, outside the lock. + if err != nil { + if !os.IsNotExist(err) { + return nil, Manifest{}, err + } + if readOnly { + return nil, Manifest{}, fmt.Errorf("no manifest found, required for read-only db") + } + m := createManifest() + fp, netCreations, err := helpRewrite(dir, &m) + if err != nil { + return nil, Manifest{}, err + } + y.AssertTrue(netCreations == 0) + mf := &manifestFile{ + fp: fp, + directory: dir, + manifest: m.clone(), + deletionsRewriteThreshold: deletionsThreshold, + } + return mf, m, nil + } + + manifest, truncOffset, err := ReplayManifestFile(fp) + if err != nil { + _ = fp.Close() + return nil, Manifest{}, err + } + + if !readOnly { + // Truncate file so we don't have a half-written entry at the end. + if err := fp.Truncate(truncOffset); err != nil { + _ = fp.Close() + return nil, Manifest{}, err + } + } + if _, err = fp.Seek(0, io.SeekEnd); err != nil { + _ = fp.Close() + return nil, Manifest{}, err + } + + mf := &manifestFile{ + fp: fp, + directory: dir, + manifest: manifest.clone(), + deletionsRewriteThreshold: deletionsThreshold, + } + return mf, manifest, nil +} + +func (mf *manifestFile) close() error { + if mf.inMemory { + return nil + } + return mf.fp.Close() +} + +// addChanges writes a batch of changes, atomically, to the file. By "atomically" that means when +// we replay the MANIFEST file, we'll either replay all the changes or none of them. (The truth of +// this depends on the filesystem -- some might append garbage data if a system crash happens at +// the wrong time.) +func (mf *manifestFile) addChanges(changesParam []*pb.ManifestChange) error { + if mf.inMemory { + return nil + } + changes := pb.ManifestChangeSet{Changes: changesParam} + buf, err := proto.Marshal(&changes) + if err != nil { + return err + } + + // Maybe we could use O_APPEND instead (on certain file systems) + mf.appendLock.Lock() + if err := applyChangeSet(&mf.manifest, &changes); err != nil { + mf.appendLock.Unlock() + return err + } + // Rewrite manifest if it'd shrink by 1/10 and it's big enough to care + if mf.manifest.Deletions > mf.deletionsRewriteThreshold && + mf.manifest.Deletions > manifestDeletionsRatio*(mf.manifest.Creations-mf.manifest.Deletions) { + if err := mf.rewrite(); err != nil { + mf.appendLock.Unlock() + return err + } + } else { + var lenCrcBuf [8]byte + binary.BigEndian.PutUint32(lenCrcBuf[0:4], uint32(len(buf))) + binary.BigEndian.PutUint32(lenCrcBuf[4:8], crc32.Checksum(buf, y.CastagnoliCrcTable)) + buf = append(lenCrcBuf[:], buf...) + if _, err := mf.fp.Write(buf); err != nil { + mf.appendLock.Unlock() + return err + } + } + + mf.appendLock.Unlock() + return mf.fp.Sync() +} + +// Has to be 4 bytes. The value can never change, ever, anyway. +var magicText = [4]byte{'B', 'd', 'g', 'r'} + +// The magic version number. +const magicVersion = 8 + +func helpRewrite(dir string, m *Manifest) (*os.File, int, error) { + rewritePath := filepath.Join(dir, manifestRewriteFilename) + // We explicitly sync. + fp, err := y.OpenTruncFile(rewritePath, false) + if err != nil { + return nil, 0, err + } + + buf := make([]byte, 8) + copy(buf[0:4], magicText[:]) + binary.BigEndian.PutUint32(buf[4:8], magicVersion) + + netCreations := len(m.Tables) + changes := m.asChanges() + set := pb.ManifestChangeSet{Changes: changes} + + changeBuf, err := proto.Marshal(&set) + if err != nil { + fp.Close() + return nil, 0, err + } + var lenCrcBuf [8]byte + binary.BigEndian.PutUint32(lenCrcBuf[0:4], uint32(len(changeBuf))) + binary.BigEndian.PutUint32(lenCrcBuf[4:8], crc32.Checksum(changeBuf, y.CastagnoliCrcTable)) + buf = append(buf, lenCrcBuf[:]...) + buf = append(buf, changeBuf...) + if _, err := fp.Write(buf); err != nil { + fp.Close() + return nil, 0, err + } + if err := fp.Sync(); err != nil { + fp.Close() + return nil, 0, err + } + + // In Windows the files should be closed before doing a Rename. + if err = fp.Close(); err != nil { + return nil, 0, err + } + manifestPath := filepath.Join(dir, ManifestFilename) + if err := os.Rename(rewritePath, manifestPath); err != nil { + return nil, 0, err + } + fp, err = y.OpenExistingFile(manifestPath, 0) + if err != nil { + return nil, 0, err + } + if _, err := fp.Seek(0, io.SeekEnd); err != nil { + fp.Close() + return nil, 0, err + } + if err := syncDir(dir); err != nil { + fp.Close() + return nil, 0, err + } + + return fp, netCreations, nil +} + +// Must be called while appendLock is held. +func (mf *manifestFile) rewrite() error { + // In Windows the files should be closed before doing a Rename. + if err := mf.fp.Close(); err != nil { + return err + } + fp, netCreations, err := helpRewrite(mf.directory, &mf.manifest) + if err != nil { + return err + } + mf.fp = fp + mf.manifest.Creations = netCreations + mf.manifest.Deletions = 0 + + return nil +} + +type countingReader struct { + wrapped *bufio.Reader + count int64 +} + +func (r *countingReader) Read(p []byte) (n int, err error) { + n, err = r.wrapped.Read(p) + r.count += int64(n) + return +} + +func (r *countingReader) ReadByte() (b byte, err error) { + b, err = r.wrapped.ReadByte() + if err == nil { + r.count++ + } + return +} + +var ( + errBadMagic = errors.New("manifest has bad magic") + errBadChecksum = errors.New("manifest has checksum mismatch") +) + +// ReplayManifestFile reads the manifest file and constructs two manifest objects. (We need one +// immutable copy and one mutable copy of the manifest. Easiest way is to construct two of them.) +// Also, returns the last offset after a completely read manifest entry -- the file must be +// truncated at that point before further appends are made (if there is a partial entry after +// that). In normal conditions, truncOffset is the file size. +func ReplayManifestFile(fp *os.File) (Manifest, int64, error) { + r := countingReader{wrapped: bufio.NewReader(fp)} + + var magicBuf [8]byte + if _, err := io.ReadFull(&r, magicBuf[:]); err != nil { + return Manifest{}, 0, errBadMagic + } + if !bytes.Equal(magicBuf[0:4], magicText[:]) { + return Manifest{}, 0, errBadMagic + } + version := y.BytesToU32(magicBuf[4:8]) + if version != magicVersion { + return Manifest{}, 0, + //nolint:lll + fmt.Errorf("manifest has unsupported version: %d (we support %d).\n"+ + "Please see https://github.com/dgraph-io/badger/blob/master/README.md#i-see-manifest-has-unsupported-version-x-we-support-y-error"+ + " on how to fix this.", + version, magicVersion) + } + + stat, err := fp.Stat() + if err != nil { + return Manifest{}, 0, err + } + + build := createManifest() + var offset int64 + for { + offset = r.count + var lenCrcBuf [8]byte + _, err := io.ReadFull(&r, lenCrcBuf[:]) + if err != nil { + if err == io.EOF || err == io.ErrUnexpectedEOF { + break + } + return Manifest{}, 0, err + } + length := y.BytesToU32(lenCrcBuf[0:4]) + // Sanity check to ensure we don't over-allocate memory. + if length > uint32(stat.Size()) { + return Manifest{}, 0, errors.Errorf( + "Buffer length: %d greater than file size: %d. Manifest file might be corrupted", + length, stat.Size()) + } + var buf = make([]byte, length) + if _, err := io.ReadFull(&r, buf); err != nil { + if err == io.EOF || err == io.ErrUnexpectedEOF { + break + } + return Manifest{}, 0, err + } + if crc32.Checksum(buf, y.CastagnoliCrcTable) != y.BytesToU32(lenCrcBuf[4:8]) { + return Manifest{}, 0, errBadChecksum + } + + var changeSet pb.ManifestChangeSet + if err := proto.Unmarshal(buf, &changeSet); err != nil { + return Manifest{}, 0, err + } + + if err := applyChangeSet(&build, &changeSet); err != nil { + return Manifest{}, 0, err + } + } + + return build, offset, nil +} + +func applyManifestChange(build *Manifest, tc *pb.ManifestChange) error { + switch tc.Op { + case pb.ManifestChange_CREATE: + if _, ok := build.Tables[tc.Id]; ok { + return fmt.Errorf("MANIFEST invalid, table %d exists", tc.Id) + } + build.Tables[tc.Id] = TableManifest{ + Level: uint8(tc.Level), + KeyID: tc.KeyId, + Compression: options.CompressionType(tc.Compression), + } + for len(build.Levels) <= int(tc.Level) { + build.Levels = append(build.Levels, levelManifest{make(map[uint64]struct{})}) + } + build.Levels[tc.Level].Tables[tc.Id] = struct{}{} + build.Creations++ + case pb.ManifestChange_DELETE: + tm, ok := build.Tables[tc.Id] + if !ok { + return fmt.Errorf("MANIFEST removes non-existing table %d", tc.Id) + } + delete(build.Levels[tm.Level].Tables, tc.Id) + delete(build.Tables, tc.Id) + build.Deletions++ + default: + return fmt.Errorf("MANIFEST file has invalid manifestChange op") + } + return nil +} + +// This is not a "recoverable" error -- opening the KV store fails because the MANIFEST file is +// just plain broken. +func applyChangeSet(build *Manifest, changeSet *pb.ManifestChangeSet) error { + for _, change := range changeSet.Changes { + if err := applyManifestChange(build, change); err != nil { + return err + } + } + return nil +} + +func newCreateChange( + id uint64, level int, keyID uint64, c options.CompressionType) *pb.ManifestChange { + return &pb.ManifestChange{ + Id: id, + Op: pb.ManifestChange_CREATE, + Level: uint32(level), + KeyId: keyID, + // Hard coding it, since we're supporting only AES for now. + EncryptionAlgo: pb.EncryptionAlgo_aes, + Compression: uint32(c), + } +} + +func newDeleteChange(id uint64) *pb.ManifestChange { + return &pb.ManifestChange{ + Id: id, + Op: pb.ManifestChange_DELETE, + } +} diff --git a/vendor/github.com/dgraph-io/badger/v3/memtable.go b/vendor/github.com/dgraph-io/badger/v3/memtable.go new file mode 100644 index 0000000000..a200fa2fa7 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/memtable.go @@ -0,0 +1,633 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bufio" + "bytes" + "crypto/aes" + cryptorand "crypto/rand" + "encoding/binary" + "fmt" + "hash/crc32" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/skl" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + "github.com/pkg/errors" +) + +// memTable structure stores a skiplist and a corresponding WAL. Writes to memTable are written +// both to the WAL and the skiplist. On a crash, the WAL is replayed to bring the skiplist back to +// its pre-crash form. +type memTable struct { + // TODO: Give skiplist z.Calloc'd []byte. + sl *skl.Skiplist + wal *logFile + maxVersion uint64 + opt Options + buf *bytes.Buffer +} + +func (db *DB) openMemTables(opt Options) error { + // We don't need to open any tables in in-memory mode. + if db.opt.InMemory { + return nil + } + files, err := ioutil.ReadDir(db.opt.Dir) + if err != nil { + return errFile(err, db.opt.Dir, "Unable to open mem dir.") + } + + var fids []int + for _, file := range files { + if !strings.HasSuffix(file.Name(), memFileExt) { + continue + } + fsz := len(file.Name()) + fid, err := strconv.ParseInt(file.Name()[:fsz-len(memFileExt)], 10, 64) + if err != nil { + return errFile(err, file.Name(), "Unable to parse log id.") + } + fids = append(fids, int(fid)) + } + + // Sort in ascending order. + sort.Slice(fids, func(i, j int) bool { + return fids[i] < fids[j] + }) + for _, fid := range fids { + flags := os.O_RDWR + if db.opt.ReadOnly { + flags = os.O_RDONLY + } + mt, err := db.openMemTable(fid, flags) + if err != nil { + return y.Wrapf(err, "while opening fid: %d", fid) + } + // If this memtable is empty we don't need to add it. This is a + // memtable that was completely truncated. + if mt.sl.Empty() { + mt.DecrRef() + continue + } + // These should no longer be written to. So, make them part of the imm. + db.imm = append(db.imm, mt) + } + if len(fids) != 0 { + db.nextMemFid = fids[len(fids)-1] + } + db.nextMemFid++ + return nil +} + +const memFileExt string = ".mem" + +func (db *DB) openMemTable(fid, flags int) (*memTable, error) { + filepath := db.mtFilePath(fid) + s := skl.NewSkiplist(arenaSize(db.opt)) + mt := &memTable{ + sl: s, + opt: db.opt, + buf: &bytes.Buffer{}, + } + // We don't need to create the wal for the skiplist in in-memory mode so return the mt. + if db.opt.InMemory { + return mt, z.NewFile + } + + mt.wal = &logFile{ + fid: uint32(fid), + path: filepath, + registry: db.registry, + writeAt: vlogHeaderSize, + opt: db.opt, + } + lerr := mt.wal.open(filepath, flags, 2*db.opt.MemTableSize) + if lerr != z.NewFile && lerr != nil { + return nil, y.Wrapf(lerr, "While opening memtable: %s", filepath) + } + + // Have a callback set to delete WAL when skiplist reference count goes down to zero. That is, + // when it gets flushed to L0. + s.OnClose = func() { + if err := mt.wal.Delete(); err != nil { + db.opt.Errorf("while deleting file: %s, err: %v", filepath, err) + } + } + + if lerr == z.NewFile { + return mt, lerr + } + err := mt.UpdateSkipList() + return mt, y.Wrapf(err, "while updating skiplist") +} + +var errExpectingNewFile = errors.New("Expecting to create a new file, but found an existing file") + +func (db *DB) newMemTable() (*memTable, error) { + mt, err := db.openMemTable(db.nextMemFid, os.O_CREATE|os.O_RDWR) + if err == z.NewFile { + db.nextMemFid++ + return mt, nil + } + + if err != nil { + db.opt.Errorf("Got error: %v for id: %d\n", err, db.nextMemFid) + return nil, y.Wrapf(err, "newMemTable") + } + return nil, errors.Errorf("File %s already exists", mt.wal.Fd.Name()) +} + +func (db *DB) mtFilePath(fid int) string { + return filepath.Join(db.opt.Dir, fmt.Sprintf("%05d%s", fid, memFileExt)) +} + +func (mt *memTable) SyncWAL() error { + return mt.wal.Sync() +} + +func (mt *memTable) isFull() bool { + if mt.sl.MemSize() >= mt.opt.MemTableSize { + return true + } + if mt.opt.InMemory { + // InMemory mode doesn't have any WAL. + return false + } + return int64(mt.wal.writeAt) >= mt.opt.MemTableSize +} + +func (mt *memTable) Put(key []byte, value y.ValueStruct) error { + entry := &Entry{ + Key: key, + Value: value.Value, + UserMeta: value.UserMeta, + meta: value.Meta, + ExpiresAt: value.ExpiresAt, + } + + // wal is nil only when badger in running in in-memory mode and we don't need the wal. + if mt.wal != nil { + // If WAL exceeds opt.ValueLogFileSize, we'll force flush the memTable. See logic in + // ensureRoomForWrite. + if err := mt.wal.writeEntry(mt.buf, entry, mt.opt); err != nil { + return y.Wrapf(err, "cannot write entry to WAL file") + } + } + // We insert the finish marker in the WAL but not in the memtable. + if entry.meta&bitFinTxn > 0 { + return nil + } + + // Write to skiplist and update maxVersion encountered. + mt.sl.Put(key, value) + if ts := y.ParseTs(entry.Key); ts > mt.maxVersion { + mt.maxVersion = ts + } + return nil +} + +func (mt *memTable) UpdateSkipList() error { + if mt.wal == nil || mt.sl == nil { + return nil + } + endOff, err := mt.wal.iterate(true, 0, mt.replayFunction(mt.opt)) + if err != nil { + return y.Wrapf(err, "while iterating wal: %s", mt.wal.Fd.Name()) + } + if endOff < mt.wal.size && mt.opt.ReadOnly { + return y.Wrapf(ErrTruncateNeeded, "end offset: %d < size: %d", endOff, mt.wal.size) + } + return mt.wal.Truncate(int64(endOff)) +} + +// IncrRef increases the refcount +func (mt *memTable) IncrRef() { + mt.sl.IncrRef() +} + +// DecrRef decrements the refcount, deallocating the Skiplist when done using it +func (mt *memTable) DecrRef() { + mt.sl.DecrRef() +} + +func (mt *memTable) replayFunction(opt Options) func(Entry, valuePointer) error { + first := true + return func(e Entry, _ valuePointer) error { // Function for replaying. + if first { + opt.Debugf("First key=%q\n", e.Key) + } + first = false + if ts := y.ParseTs(e.Key); ts > mt.maxVersion { + mt.maxVersion = ts + } + v := y.ValueStruct{ + Value: e.Value, + Meta: e.meta, + UserMeta: e.UserMeta, + ExpiresAt: e.ExpiresAt, + } + // This is already encoded correctly. Value would be either a vptr, or a full value + // depending upon how big the original value was. Skiplist makes a copy of the key and + // value. + mt.sl.Put(e.Key, v) + return nil + } +} + +type logFile struct { + *z.MmapFile + path string + // This is a lock on the log file. It guards the fd’s value, the file’s + // existence and the file’s memory map. + // + // Use shared ownership when reading/writing the file or memory map, use + // exclusive ownership to open/close the descriptor, unmap or remove the file. + lock sync.RWMutex + fid uint32 + size uint32 + dataKey *pb.DataKey + baseIV []byte + registry *KeyRegistry + writeAt uint32 + opt Options +} + +func (lf *logFile) Truncate(end int64) error { + if fi, err := lf.Fd.Stat(); err != nil { + return fmt.Errorf("while file.stat on file: %s, error: %v\n", lf.Fd.Name(), err) + } else if fi.Size() == end { + return nil + } + y.AssertTrue(!lf.opt.ReadOnly) + lf.size = uint32(end) + return lf.MmapFile.Truncate(end) +} + +// encodeEntry will encode entry to the buf +// layout of entry +// +--------+-----+-------+-------+ +// | header | key | value | crc32 | +// +--------+-----+-------+-------+ +func (lf *logFile) encodeEntry(buf *bytes.Buffer, e *Entry, offset uint32) (int, error) { + h := header{ + klen: uint32(len(e.Key)), + vlen: uint32(len(e.Value)), + expiresAt: e.ExpiresAt, + meta: e.meta, + userMeta: e.UserMeta, + } + + hash := crc32.New(y.CastagnoliCrcTable) + writer := io.MultiWriter(buf, hash) + + // encode header. + var headerEnc [maxHeaderSize]byte + sz := h.Encode(headerEnc[:]) + y.Check2(writer.Write(headerEnc[:sz])) + // we'll encrypt only key and value. + if lf.encryptionEnabled() { + // TODO: no need to allocate the bytes. we can calculate the encrypted buf one by one + // since we're using ctr mode of AES encryption. Ordering won't changed. Need some + // refactoring in XORBlock which will work like stream cipher. + eBuf := make([]byte, 0, len(e.Key)+len(e.Value)) + eBuf = append(eBuf, e.Key...) + eBuf = append(eBuf, e.Value...) + if err := y.XORBlockStream( + writer, eBuf, lf.dataKey.Data, lf.generateIV(offset)); err != nil { + return 0, y.Wrapf(err, "Error while encoding entry for vlog.") + } + } else { + // Encryption is disabled so writing directly to the buffer. + y.Check2(writer.Write(e.Key)) + y.Check2(writer.Write(e.Value)) + } + // write crc32 hash. + var crcBuf [crc32.Size]byte + binary.BigEndian.PutUint32(crcBuf[:], hash.Sum32()) + y.Check2(buf.Write(crcBuf[:])) + // return encoded length. + return len(headerEnc[:sz]) + len(e.Key) + len(e.Value) + len(crcBuf), nil +} + +func (lf *logFile) writeEntry(buf *bytes.Buffer, e *Entry, opt Options) error { + buf.Reset() + plen, err := lf.encodeEntry(buf, e, lf.writeAt) + if err != nil { + return err + } + y.AssertTrue(plen == copy(lf.Data[lf.writeAt:], buf.Bytes())) + lf.writeAt += uint32(plen) + + lf.zeroNextEntry() + return nil +} + +func (lf *logFile) decodeEntry(buf []byte, offset uint32) (*Entry, error) { + var h header + hlen := h.Decode(buf) + kv := buf[hlen:] + if lf.encryptionEnabled() { + var err error + // No need to worry about mmap. because, XORBlock allocates a byte array to do the + // xor. So, the given slice is not being mutated. + if kv, err = lf.decryptKV(kv, offset); err != nil { + return nil, err + } + } + e := &Entry{ + meta: h.meta, + UserMeta: h.userMeta, + ExpiresAt: h.expiresAt, + offset: offset, + Key: kv[:h.klen], + Value: kv[h.klen : h.klen+h.vlen], + } + return e, nil +} + +func (lf *logFile) decryptKV(buf []byte, offset uint32) ([]byte, error) { + return y.XORBlockAllocate(buf, lf.dataKey.Data, lf.generateIV(offset)) +} + +// KeyID returns datakey's ID. +func (lf *logFile) keyID() uint64 { + if lf.dataKey == nil { + // If there is no datakey, then we'll return 0. Which means no encryption. + return 0 + } + return lf.dataKey.KeyId +} + +func (lf *logFile) encryptionEnabled() bool { + return lf.dataKey != nil +} + +// Acquire lock on mmap/file if you are calling this +func (lf *logFile) read(p valuePointer) (buf []byte, err error) { + var nbr int64 + offset := p.Offset + // Do not convert size to uint32, because the lf.Data can be of size + // 4GB, which overflows the uint32 during conversion to make the size 0, + // causing the read to fail with ErrEOF. See issue #585. + size := int64(len(lf.Data)) + valsz := p.Len + lfsz := atomic.LoadUint32(&lf.size) + if int64(offset) >= size || int64(offset+valsz) > size || + // Ensure that the read is within the file's actual size. It might be possible that + // the offset+valsz length is beyond the file's actual size. This could happen when + // dropAll and iterations are running simultaneously. + int64(offset+valsz) > int64(lfsz) { + err = y.ErrEOF + } else { + buf = lf.Data[offset : offset+valsz] + nbr = int64(valsz) + } + y.NumReadsAdd(lf.opt.MetricsEnabled, 1) + y.NumBytesReadAdd(lf.opt.MetricsEnabled, nbr) + return buf, err +} + +// generateIV will generate IV by appending given offset with the base IV. +func (lf *logFile) generateIV(offset uint32) []byte { + iv := make([]byte, aes.BlockSize) + // baseIV is of 12 bytes. + y.AssertTrue(12 == copy(iv[:12], lf.baseIV)) + // remaining 4 bytes is obtained from offset. + binary.BigEndian.PutUint32(iv[12:], offset) + return iv +} + +func (lf *logFile) doneWriting(offset uint32) error { + if lf.opt.SyncWrites { + if err := lf.Sync(); err != nil { + return y.Wrapf(err, "Unable to sync value log: %q", lf.path) + } + } + + // Before we were acquiring a lock here on lf.lock, because we were invalidating the file + // descriptor due to reopening it as read-only. Now, we don't invalidate the fd, but unmap it, + // truncate it and remap it. That creates a window where we have segfaults because the mmap is + // no longer valid, while someone might be reading it. Therefore, we need a lock here again. + lf.lock.Lock() + defer lf.lock.Unlock() + + if err := lf.Truncate(int64(offset)); err != nil { + return y.Wrapf(err, "Unable to truncate file: %q", lf.path) + } + + // Previously we used to close the file after it was written and reopen it in read-only mode. + // We no longer open files in read-only mode. We keep all vlog files open in read-write mode. + return nil +} + +// iterate iterates over log file. It doesn't not allocate new memory for every kv pair. +// Therefore, the kv pair is only valid for the duration of fn call. +func (lf *logFile) iterate(readOnly bool, offset uint32, fn logEntry) (uint32, error) { + if offset == 0 { + // If offset is set to zero, let's advance past the encryption key header. + offset = vlogHeaderSize + } + + // For now, read directly from file, because it allows + reader := bufio.NewReader(lf.NewReader(int(offset))) + read := &safeRead{ + k: make([]byte, 10), + v: make([]byte, 10), + recordOffset: offset, + lf: lf, + } + + var lastCommit uint64 + var validEndOffset uint32 = offset + + var entries []*Entry + var vptrs []valuePointer + +loop: + for { + e, err := read.Entry(reader) + switch { + // We have not reached the end of the file but the entry we read is + // zero. This happens because we have truncated the file and + // zero'ed it out. + case err == io.EOF: + break loop + case err == io.ErrUnexpectedEOF || err == errTruncate: + break loop + case err != nil: + return 0, err + case e == nil: + continue + case e.isZero(): + break loop + } + + var vp valuePointer + vp.Len = uint32(int(e.hlen) + len(e.Key) + len(e.Value) + crc32.Size) + read.recordOffset += vp.Len + + vp.Offset = e.offset + vp.Fid = lf.fid + + switch { + case e.meta&bitTxn > 0: + txnTs := y.ParseTs(e.Key) + if lastCommit == 0 { + lastCommit = txnTs + } + if lastCommit != txnTs { + break loop + } + entries = append(entries, e) + vptrs = append(vptrs, vp) + + case e.meta&bitFinTxn > 0: + txnTs, err := strconv.ParseUint(string(e.Value), 10, 64) + if err != nil || lastCommit != txnTs { + break loop + } + // Got the end of txn. Now we can store them. + lastCommit = 0 + validEndOffset = read.recordOffset + + for i, e := range entries { + vp := vptrs[i] + if err := fn(*e, vp); err != nil { + if err == errStop { + break + } + return 0, errFile(err, lf.path, "Iteration function") + } + } + entries = entries[:0] + vptrs = vptrs[:0] + + default: + if lastCommit != 0 { + // This is most likely an entry which was moved as part of GC. + // We shouldn't get this entry in the middle of a transaction. + break loop + } + validEndOffset = read.recordOffset + + if err := fn(*e, vp); err != nil { + if err == errStop { + break + } + return 0, errFile(err, lf.path, "Iteration function") + } + } + } + return validEndOffset, nil +} + +// Zero out the next entry to deal with any crashes. +func (lf *logFile) zeroNextEntry() { + z.ZeroOut(lf.Data, int(lf.writeAt), int(lf.writeAt+maxHeaderSize)) +} + +func (lf *logFile) open(path string, flags int, fsize int64) error { + mf, ferr := z.OpenMmapFile(path, flags, int(fsize)) + lf.MmapFile = mf + + if ferr == z.NewFile { + if err := lf.bootstrap(); err != nil { + os.Remove(path) + return err + } + lf.size = vlogHeaderSize + + } else if ferr != nil { + return y.Wrapf(ferr, "while opening file: %s", path) + } + lf.size = uint32(len(lf.Data)) + + if lf.size < vlogHeaderSize { + // Every vlog file should have at least vlogHeaderSize. If it is less than vlogHeaderSize + // then it must have been corrupted. But no need to handle here. log replayer will truncate + // and bootstrap the logfile. So ignoring here. + return nil + } + + // Copy over the encryption registry data. + buf := make([]byte, vlogHeaderSize) + + y.AssertTruef(vlogHeaderSize == copy(buf, lf.Data), + "Unable to copy from %s, size %d", path, lf.size) + keyID := binary.BigEndian.Uint64(buf[:8]) + // retrieve datakey. + if dk, err := lf.registry.DataKey(keyID); err != nil { + return y.Wrapf(err, "While opening vlog file %d", lf.fid) + } else { + lf.dataKey = dk + } + lf.baseIV = buf[8:] + y.AssertTrue(len(lf.baseIV) == 12) + + // Preserved ferr so we can return if this was a new file. + return ferr +} + +// bootstrap will initialize the log file with key id and baseIV. +// The below figure shows the layout of log file. +// +----------------+------------------+------------------+ +// | keyID(8 bytes) | baseIV(12 bytes)| entry... | +// +----------------+------------------+------------------+ +func (lf *logFile) bootstrap() error { + var err error + + // generate data key for the log file. + var dk *pb.DataKey + if dk, err = lf.registry.LatestDataKey(); err != nil { + return y.Wrapf(err, "Error while retrieving datakey in logFile.bootstarp") + } + lf.dataKey = dk + + // We'll always preserve vlogHeaderSize for key id and baseIV. + buf := make([]byte, vlogHeaderSize) + + // write key id to the buf. + // key id will be zero if the logfile is in plain text. + binary.BigEndian.PutUint64(buf[:8], lf.keyID()) + // generate base IV. It'll be used with offset of the vptr to encrypt the entry. + if _, err := cryptorand.Read(buf[8:]); err != nil { + return y.Wrapf(err, "Error while creating base IV, while creating logfile") + } + + // Initialize base IV. + lf.baseIV = buf[8:] + y.AssertTrue(len(lf.baseIV) == 12) + + // Copy over to the logFile. + y.AssertTrue(vlogHeaderSize == copy(lf.Data[0:], buf)) + + // Zero out the next entry. + lf.zeroNextEntry() + return nil +} diff --git a/vendor/github.com/dgraph-io/badger/v3/merge.go b/vendor/github.com/dgraph-io/badger/v3/merge.go new file mode 100644 index 0000000000..ac1a2b51e7 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/merge.go @@ -0,0 +1,181 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "sync" + "time" + + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + "github.com/pkg/errors" +) + +// MergeOperator represents a Badger merge operator. +type MergeOperator struct { + sync.RWMutex + f MergeFunc + db *DB + key []byte + closer *z.Closer +} + +// MergeFunc accepts two byte slices, one representing an existing value, and +// another representing a new value that needs to be ‘merged’ into it. MergeFunc +// contains the logic to perform the ‘merge’ and return an updated value. +// MergeFunc could perform operations like integer addition, list appends etc. +// Note that the ordering of the operands is maintained. +type MergeFunc func(existingVal, newVal []byte) []byte + +// GetMergeOperator creates a new MergeOperator for a given key and returns a +// pointer to it. It also fires off a goroutine that performs a compaction using +// the merge function that runs periodically, as specified by dur. +func (db *DB) GetMergeOperator(key []byte, + f MergeFunc, dur time.Duration) *MergeOperator { + op := &MergeOperator{ + f: f, + db: db, + key: key, + closer: z.NewCloser(1), + } + + go op.runCompactions(dur) + return op +} + +var errNoMerge = errors.New("No need for merge") + +func (op *MergeOperator) iterateAndMerge() (newVal []byte, latest uint64, err error) { + txn := op.db.NewTransaction(false) + defer txn.Discard() + opt := DefaultIteratorOptions + opt.AllVersions = true + it := txn.NewKeyIterator(op.key, opt) + defer it.Close() + + var numVersions int + for it.Rewind(); it.Valid(); it.Next() { + item := it.Item() + if item.IsDeletedOrExpired() { + break + } + numVersions++ + if numVersions == 1 { + // This should be the newVal, considering this is the latest version. + newVal, err = item.ValueCopy(newVal) + if err != nil { + return nil, 0, err + } + latest = item.Version() + } else { + if err := item.Value(func(oldVal []byte) error { + // The merge should always be on the newVal considering it has the merge result of + // the latest version. The value read should be the oldVal. + newVal = op.f(oldVal, newVal) + return nil + }); err != nil { + return nil, 0, err + } + } + if item.DiscardEarlierVersions() { + break + } + } + if numVersions == 0 { + return nil, latest, ErrKeyNotFound + } else if numVersions == 1 { + return newVal, latest, errNoMerge + } + return newVal, latest, nil +} + +func (op *MergeOperator) compact() error { + op.Lock() + defer op.Unlock() + val, version, err := op.iterateAndMerge() + if err == ErrKeyNotFound || err == errNoMerge { + return nil + } else if err != nil { + return err + } + entries := []*Entry{ + { + Key: y.KeyWithTs(op.key, version), + Value: val, + meta: bitDiscardEarlierVersions, + }, + } + // Write value back to the DB. It is important that we do not set the bitMergeEntry bit + // here. When compaction happens, all the older merged entries will be removed. + return op.db.batchSetAsync(entries, func(err error) { + if err != nil { + op.db.opt.Errorf("failed to insert the result of merge compaction: %s", err) + } + }) +} + +func (op *MergeOperator) runCompactions(dur time.Duration) { + ticker := time.NewTicker(dur) + defer op.closer.Done() + var stop bool + for { + select { + case <-op.closer.HasBeenClosed(): + stop = true + case <-ticker.C: // wait for tick + } + if err := op.compact(); err != nil { + op.db.opt.Errorf("failure while running merge operation: %s", err) + } + if stop { + ticker.Stop() + break + } + } +} + +// Add records a value in Badger which will eventually be merged by a background +// routine into the values that were recorded by previous invocations to Add(). +func (op *MergeOperator) Add(val []byte) error { + return op.db.Update(func(txn *Txn) error { + return txn.SetEntry(NewEntry(op.key, val).withMergeBit()) + }) +} + +// Get returns the latest value for the merge operator, which is derived by +// applying the merge function to all the values added so far. +// +// If Add has not been called even once, Get will return ErrKeyNotFound. +func (op *MergeOperator) Get() ([]byte, error) { + op.RLock() + defer op.RUnlock() + var existing []byte + err := op.db.View(func(txn *Txn) (err error) { + existing, _, err = op.iterateAndMerge() + return err + }) + if err == errNoMerge { + return existing, nil + } + return existing, err +} + +// Stop waits for any pending merge to complete and then stops the background +// goroutine. +func (op *MergeOperator) Stop() { + op.closer.SignalAndWait() +} diff --git a/vendor/github.com/dgraph-io/badger/v3/options.go b/vendor/github.com/dgraph-io/badger/v3/options.go new file mode 100644 index 0000000000..2963355608 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/options.go @@ -0,0 +1,791 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "fmt" + "os" + "reflect" + "strconv" + "strings" + "time" + + "github.com/dgraph-io/ristretto/z" + "github.com/pkg/errors" + + "github.com/dgraph-io/badger/v3/options" + "github.com/dgraph-io/badger/v3/table" + "github.com/dgraph-io/badger/v3/y" +) + +// Note: If you add a new option X make sure you also add a WithX method on Options. + +// Options are params for creating DB object. +// +// This package provides DefaultOptions which contains options that should +// work for most applications. Consider using that as a starting point before +// customizing it for your own needs. +// +// Each option X is documented on the WithX method. +type Options struct { + // Required options. + + Dir string + ValueDir string + + // Usually modified options. + + SyncWrites bool + NumVersionsToKeep int + ReadOnly bool + Logger Logger + Compression options.CompressionType + InMemory bool + MetricsEnabled bool + // Sets the Stream.numGo field + NumGoroutines int + + // Fine tuning options. + + MemTableSize int64 + BaseTableSize int64 + BaseLevelSize int64 + LevelSizeMultiplier int + TableSizeMultiplier int + MaxLevels int + + VLogPercentile float64 + ValueThreshold int64 + NumMemtables int + // Changing BlockSize across DB runs will not break badger. The block size is + // read from the block index stored at the end of the table. + BlockSize int + BloomFalsePositive float64 + BlockCacheSize int64 + IndexCacheSize int64 + + NumLevelZeroTables int + NumLevelZeroTablesStall int + + ValueLogFileSize int64 + ValueLogMaxEntries uint32 + + NumCompactors int + CompactL0OnClose bool + LmaxCompaction bool + ZSTDCompressionLevel int + + // When set, checksum will be validated for each entry read from the value log file. + VerifyValueChecksum bool + + // Encryption related options. + EncryptionKey []byte // encryption key + EncryptionKeyRotationDuration time.Duration // key rotation duration + + // BypassLockGuard will bypass the lock guard on badger. Bypassing lock + // guard can cause data corruption if multiple badger instances are using + // the same directory. Use this options with caution. + BypassLockGuard bool + + // ChecksumVerificationMode decides when db should verify checksums for SSTable blocks. + ChecksumVerificationMode options.ChecksumVerificationMode + + // DetectConflicts determines whether the transactions would be checked for + // conflicts. The transactions can be processed at a higher rate when + // conflict detection is disabled. + DetectConflicts bool + + // NamespaceOffset specifies the offset from where the next 8 bytes contains the namespace. + NamespaceOffset int + + // Transaction start and commit timestamps are managed by end-user. + // This is only useful for databases built on top of Badger (like Dgraph). + // Not recommended for most users. + managedTxns bool + + // 4. Flags for testing purposes + // ------------------------------ + maxBatchCount int64 // max entries in batch + maxBatchSize int64 // max batch size in bytes + + maxValueThreshold float64 +} + +// DefaultOptions sets a list of recommended options for good performance. +// Feel free to modify these to suit your needs with the WithX methods. +func DefaultOptions(path string) Options { + return Options{ + Dir: path, + ValueDir: path, + + MemTableSize: 64 << 20, + BaseTableSize: 2 << 20, + BaseLevelSize: 10 << 20, + TableSizeMultiplier: 2, + LevelSizeMultiplier: 10, + MaxLevels: 7, + NumGoroutines: 8, + MetricsEnabled: true, + + NumCompactors: 4, // Run at least 2 compactors. Zero-th compactor prioritizes L0. + NumLevelZeroTables: 5, + NumLevelZeroTablesStall: 15, + NumMemtables: 5, + BloomFalsePositive: 0.01, + BlockSize: 4 * 1024, + SyncWrites: false, + NumVersionsToKeep: 1, + CompactL0OnClose: false, + VerifyValueChecksum: false, + Compression: options.Snappy, + BlockCacheSize: 256 << 20, + IndexCacheSize: 0, + + // The following benchmarks were done on a 4 KB block size (default block size). The + // compression is ratio supposed to increase with increasing compression level but since the + // input for compression algorithm is small (4 KB), we don't get significant benefit at + // level 3. + // NOTE: The benchmarks are with DataDog ZSTD that requires CGO. Hence, no longer valid. + // no_compression-16 10 502848865 ns/op 165.46 MB/s - + // zstd_compression/level_1-16 7 739037966 ns/op 112.58 MB/s 2.93 + // zstd_compression/level_3-16 7 756950250 ns/op 109.91 MB/s 2.72 + // zstd_compression/level_15-16 1 11135686219 ns/op 7.47 MB/s 4.38 + // Benchmark code can be found in table/builder_test.go file + ZSTDCompressionLevel: 1, + + // Nothing to read/write value log using standard File I/O + // MemoryMap to mmap() the value log files + // (2^30 - 1)*2 when mmapping < 2^31 - 1, max int32. + // -1 so 2*ValueLogFileSize won't overflow on 32-bit systems. + ValueLogFileSize: 1<<30 - 1, + + ValueLogMaxEntries: 1000000, + + VLogPercentile: 0.0, + ValueThreshold: maxValueThreshold, + + Logger: defaultLogger(INFO), + EncryptionKey: []byte{}, + EncryptionKeyRotationDuration: 10 * 24 * time.Hour, // Default 10 days. + DetectConflicts: true, + NamespaceOffset: -1, + } +} + +func buildTableOptions(db *DB) table.Options { + opt := db.opt + dk, err := db.registry.LatestDataKey() + y.Check(err) + return table.Options{ + ReadOnly: opt.ReadOnly, + MetricsEnabled: db.opt.MetricsEnabled, + TableSize: uint64(opt.BaseTableSize), + BlockSize: opt.BlockSize, + BloomFalsePositive: opt.BloomFalsePositive, + ChkMode: opt.ChecksumVerificationMode, + Compression: opt.Compression, + ZSTDCompressionLevel: opt.ZSTDCompressionLevel, + BlockCache: db.blockCache, + IndexCache: db.indexCache, + AllocPool: db.allocPool, + DataKey: dk, + } +} + +const ( + maxValueThreshold = (1 << 20) // 1 MB +) + +// LSMOnlyOptions follows from DefaultOptions, but sets a higher ValueThreshold +// so values would be collocated with the LSM tree, with value log largely acting +// as a write-ahead log only. These options would reduce the disk usage of value +// log, and make Badger act more like a typical LSM tree. +func LSMOnlyOptions(path string) Options { + // Let's not set any other options, because they can cause issues with the + // size of key-value a user can pass to Badger. For e.g., if we set + // ValueLogFileSize to 64MB, a user can't pass a value more than that. + // Setting it to ValueLogMaxEntries to 1000, can generate too many files. + // These options are better configured on a usage basis, than broadly here. + // The ValueThreshold is the most important setting a user needs to do to + // achieve a heavier usage of LSM tree. + // NOTE: If a user does not want to set 64KB as the ValueThreshold because + // of performance reasons, 1KB would be a good option too, allowing + // values smaller than 1KB to be collocated with the keys in the LSM tree. + return DefaultOptions(path).WithValueThreshold(maxValueThreshold /* 1 MB */) +} + +// parseCompression returns badger.compressionType and compression level given compression string +// of format compression-type:compression-level +func parseCompression(cStr string) (options.CompressionType, int, error) { + cStrSplit := strings.Split(cStr, ":") + cType := cStrSplit[0] + level := 3 + + var err error + if len(cStrSplit) == 2 { + level, err = strconv.Atoi(cStrSplit[1]) + y.Check(err) + if level <= 0 { + return 0, 0, + errors.Errorf("ERROR: compression level(%v) must be greater than zero", level) + } + } else if len(cStrSplit) > 2 { + return 0, 0, errors.Errorf("ERROR: Invalid badger.compression argument") + } + switch cType { + case "zstd": + return options.ZSTD, level, nil + case "snappy": + return options.Snappy, 0, nil + case "none": + return options.None, 0, nil + } + return 0, 0, errors.Errorf("ERROR: compression type (%s) invalid", cType) +} + +// generateSuperFlag generates an identical SuperFlag string from the provided Options. +func generateSuperFlag(options Options) string { + superflag := "" + v := reflect.ValueOf(&options).Elem() + optionsStruct := v.Type() + for i := 0; i < v.NumField(); i++ { + if field := v.Field(i); field.CanInterface() { + name := strings.ToLower(optionsStruct.Field(i).Name) + kind := v.Field(i).Kind() + switch kind { + case reflect.Bool: + superflag += name + "=" + superflag += fmt.Sprintf("%v; ", field.Bool()) + case reflect.Int, reflect.Int64: + superflag += name + "=" + superflag += fmt.Sprintf("%v; ", field.Int()) + case reflect.Uint32, reflect.Uint64: + superflag += name + "=" + superflag += fmt.Sprintf("%v; ", field.Uint()) + case reflect.Float64: + superflag += name + "=" + superflag += fmt.Sprintf("%v; ", field.Float()) + case reflect.String: + superflag += name + "=" + superflag += fmt.Sprintf("%v; ", field.String()) + default: + continue + } + } + } + return superflag +} + +// FromSuperFlag fills Options fields for each flag within the superflag. For +// example, replacing the default Options.NumGoroutines: +// +// options := FromSuperFlag("numgoroutines=4", DefaultOptions("")) +// +// It's important to note that if you pass an empty Options struct, FromSuperFlag +// will not fill it with default values. FromSuperFlag only writes to the fields +// present within the superflag string (case insensitive). +// +// It specially handles compression subflag. +// Valid options are {none,snappy,zstd:} +// Example: compression=zstd:3; +// Unsupported: Options.Logger, Options.EncryptionKey +func (opt Options) FromSuperFlag(superflag string) Options { + // currentOptions act as a default value for the options superflag. + currentOptions := generateSuperFlag(opt) + currentOptions += "compression=;" + + flags := z.NewSuperFlag(superflag).MergeAndCheckDefault(currentOptions) + v := reflect.ValueOf(&opt).Elem() + optionsStruct := v.Type() + for i := 0; i < v.NumField(); i++ { + // only iterate over exported fields + if field := v.Field(i); field.CanInterface() { + // z.SuperFlag stores keys as lowercase, keep everything case + // insensitive + name := strings.ToLower(optionsStruct.Field(i).Name) + if name == "compression" { + // We will specially handle this later. Skip it here. + continue + } + kind := v.Field(i).Kind() + switch kind { + case reflect.Bool: + field.SetBool(flags.GetBool(name)) + case reflect.Int, reflect.Int64: + field.SetInt(flags.GetInt64(name)) + case reflect.Uint32, reflect.Uint64: + field.SetUint(uint64(flags.GetUint64(name))) + case reflect.Float64: + field.SetFloat(flags.GetFloat64(name)) + case reflect.String: + field.SetString(flags.GetString(name)) + } + } + } + + // Only update the options for special flags that were present in the input superflag. + inputFlag := z.NewSuperFlag(superflag) + if inputFlag.Has("compression") { + ctype, clevel, err := parseCompression(flags.GetString("compression")) + switch err { + case nil: + opt.Compression = ctype + opt.ZSTDCompressionLevel = clevel + default: + ctype = options.CompressionType(flags.GetUint32("compression")) + y.AssertTruef(ctype <= 2, "ERROR: Invalid format or compression type. Got: %s", + flags.GetString("compression")) + opt.Compression = ctype + } + } + + return opt +} + +// WithDir returns a new Options value with Dir set to the given value. +// +// Dir is the path of the directory where key data will be stored in. +// If it doesn't exist, Badger will try to create it for you. +// This is set automatically to be the path given to `DefaultOptions`. +func (opt Options) WithDir(val string) Options { + opt.Dir = val + return opt +} + +// WithValueDir returns a new Options value with ValueDir set to the given value. +// +// ValueDir is the path of the directory where value data will be stored in. +// If it doesn't exist, Badger will try to create it for you. +// This is set automatically to be the path given to `DefaultOptions`. +func (opt Options) WithValueDir(val string) Options { + opt.ValueDir = val + return opt +} + +// WithSyncWrites returns a new Options value with SyncWrites set to the given value. +// +// Badger does all writes via mmap. So, all writes can survive process crashes or k8s environments +// with SyncWrites set to false. +// +// When set to true, Badger would call an additional msync after writes to flush mmap buffer over to +// disk to survive hard reboots. Most users of Badger should not need to do this. +// +// The default value of SyncWrites is false. +func (opt Options) WithSyncWrites(val bool) Options { + opt.SyncWrites = val + return opt +} + +// WithNumVersionsToKeep returns a new Options value with NumVersionsToKeep set to the given value. +// +// NumVersionsToKeep sets how many versions to keep per key at most. +// +// The default value of NumVersionsToKeep is 1. +func (opt Options) WithNumVersionsToKeep(val int) Options { + opt.NumVersionsToKeep = val + return opt +} + +// WithNumGoroutines sets the number of goroutines to be used in Stream. +// +// The default value of NumGoroutines is 8. +func (opt Options) WithNumGoroutines(val int) Options { + opt.NumGoroutines = val + return opt +} + +// WithReadOnly returns a new Options value with ReadOnly set to the given value. +// +// When ReadOnly is true the DB will be opened on read-only mode. +// Multiple processes can open the same Badger DB. +// Note: if the DB being opened had crashed before and has vlog data to be replayed, +// ReadOnly will cause Open to fail with an appropriate message. +// +// The default value of ReadOnly is false. +func (opt Options) WithReadOnly(val bool) Options { + opt.ReadOnly = val + return opt +} + +// WithMetricsEnabled returns a new Options value with MetricsEnabled set to the given value. +// +// When MetricsEnabled is set to false, then the DB will be opened and no badger metrics +// will be logged. Metrics are defined in metric.go file. +// +// This flag is useful for use cases like in Dgraph where we open temporary badger instances to +// index data. In those cases we don't want badger metrics to be polluted with the noise from +// those temporary instances. +// +// Default value is set to true +func (opt Options) WithMetricsEnabled(val bool) Options { + opt.MetricsEnabled = val + return opt +} + +// WithLogger returns a new Options value with Logger set to the given value. +// +// Logger provides a way to configure what logger each value of badger.DB uses. +// +// The default value of Logger writes to stderr using the log package from the Go standard library. +func (opt Options) WithLogger(val Logger) Options { + opt.Logger = val + return opt +} + +// WithLoggingLevel returns a new Options value with logging level of the +// default logger set to the given value. +// LoggingLevel sets the level of logging. It should be one of DEBUG, INFO, +// WARNING or ERROR levels. +// +// The default value of LoggingLevel is INFO. +func (opt Options) WithLoggingLevel(val loggingLevel) Options { + opt.Logger = defaultLogger(val) + return opt +} + +// WithBaseTableSize returns a new Options value with MaxTableSize set to the given value. +// +// BaseTableSize sets the maximum size in bytes for LSM table or file in the base level. +// +// The default value of BaseTableSize is 2MB. +func (opt Options) WithBaseTableSize(val int64) Options { + opt.BaseTableSize = val + return opt +} + +// WithLevelSizeMultiplier returns a new Options value with LevelSizeMultiplier set to the given +// value. +// +// LevelSizeMultiplier sets the ratio between the maximum sizes of contiguous levels in the LSM. +// Once a level grows to be larger than this ratio allowed, the compaction process will be +// triggered. +// +// The default value of LevelSizeMultiplier is 10. +func (opt Options) WithLevelSizeMultiplier(val int) Options { + opt.LevelSizeMultiplier = val + return opt +} + +// WithMaxLevels returns a new Options value with MaxLevels set to the given value. +// +// Maximum number of levels of compaction allowed in the LSM. +// +// The default value of MaxLevels is 7. +func (opt Options) WithMaxLevels(val int) Options { + opt.MaxLevels = val + return opt +} + +// WithValueThreshold returns a new Options value with ValueThreshold set to the given value. +// +// ValueThreshold sets the threshold used to decide whether a value is stored directly in the LSM +// tree or separately in the log value files. +// +// The default value of ValueThreshold is 1 MB, but LSMOnlyOptions sets it to maxValueThreshold. +func (opt Options) WithValueThreshold(val int64) Options { + opt.ValueThreshold = val + return opt +} + +// WithVLogPercentile returns a new Options value with ValLogPercentile set to given value. +// +// VLogPercentile with 0.0 means no dynamic thresholding is enabled. +// MinThreshold value will always act as the value threshold. +// +// VLogPercentile with value 0.99 means 99 percentile of value will be put in LSM tree +// and only 1 percent in vlog. The value threshold will be dynamically updated within the range of +// [ValueThreshold, Options.maxValueThreshold] +// +// Say VLogPercentile with 1.0 means threshold will eventually set to Options.maxValueThreshold +// +// The default value of VLogPercentile is 0.0. +func (opt Options) WithVLogPercentile(t float64) Options { + opt.VLogPercentile = t + return opt +} + +// WithNumMemtables returns a new Options value with NumMemtables set to the given value. +// +// NumMemtables sets the maximum number of tables to keep in memory before stalling. +// +// The default value of NumMemtables is 5. +func (opt Options) WithNumMemtables(val int) Options { + opt.NumMemtables = val + return opt +} + +// WithMemTableSize returns a new Options value with MemTableSize set to the given value. +// +// MemTableSize sets the maximum size in bytes for memtable table. +// +// The default value of MemTableSize is 64MB. +func (opt Options) WithMemTableSize(val int64) Options { + opt.MemTableSize = val + return opt +} + +// WithBloomFalsePositive returns a new Options value with BloomFalsePositive set +// to the given value. +// +// BloomFalsePositive sets the false positive probability of the bloom filter in any SSTable. +// Before reading a key from table, the bloom filter is checked for key existence. +// BloomFalsePositive might impact read performance of DB. Lower BloomFalsePositive value might +// consume more memory. +// +// The default value of BloomFalsePositive is 0.01. +// +// Setting this to 0 disables the bloom filter completely. +func (opt Options) WithBloomFalsePositive(val float64) Options { + opt.BloomFalsePositive = val + return opt +} + +// WithBlockSize returns a new Options value with BlockSize set to the given value. +// +// BlockSize sets the size of any block in SSTable. SSTable is divided into multiple blocks +// internally. Each block is compressed using prefix diff encoding. +// +// The default value of BlockSize is 4KB. +func (opt Options) WithBlockSize(val int) Options { + opt.BlockSize = val + return opt +} + +// WithNumLevelZeroTables sets the maximum number of Level 0 tables before compaction starts. +// +// The default value of NumLevelZeroTables is 5. +func (opt Options) WithNumLevelZeroTables(val int) Options { + opt.NumLevelZeroTables = val + return opt +} + +// WithNumLevelZeroTablesStall sets the number of Level 0 tables that once reached causes the DB to +// stall until compaction succeeds. +// +// The default value of NumLevelZeroTablesStall is 10. +func (opt Options) WithNumLevelZeroTablesStall(val int) Options { + opt.NumLevelZeroTablesStall = val + return opt +} + +// WithBaseLevelSize sets the maximum size target for the base level. +// +// The default value is 10MB. +func (opt Options) WithBaseLevelSize(val int64) Options { + opt.BaseLevelSize = val + return opt +} + +// WithValueLogFileSize sets the maximum size of a single value log file. +// +// The default value of ValueLogFileSize is 1GB. +func (opt Options) WithValueLogFileSize(val int64) Options { + opt.ValueLogFileSize = val + return opt +} + +// WithValueLogMaxEntries sets the maximum number of entries a value log file +// can hold approximately. A actual size limit of a value log file is the +// minimum of ValueLogFileSize and ValueLogMaxEntries. +// +// The default value of ValueLogMaxEntries is one million (1000000). +func (opt Options) WithValueLogMaxEntries(val uint32) Options { + opt.ValueLogMaxEntries = val + return opt +} + +// WithNumCompactors sets the number of compaction workers to run concurrently. Setting this to +// zero stops compactions, which could eventually cause writes to block forever. +// +// The default value of NumCompactors is 2. One is dedicated just for L0 and L1. +func (opt Options) WithNumCompactors(val int) Options { + opt.NumCompactors = val + return opt +} + +// WithCompactL0OnClose determines whether Level 0 should be compacted before closing the DB. This +// ensures that both reads and writes are efficient when the DB is opened later. +// +// The default value of CompactL0OnClose is false. +func (opt Options) WithCompactL0OnClose(val bool) Options { + opt.CompactL0OnClose = val + return opt +} + +// WithEncryptionKey is used to encrypt the data with AES. Type of AES is used based on the key +// size. For example 16 bytes will use AES-128. 24 bytes will use AES-192. 32 bytes will +// use AES-256. +func (opt Options) WithEncryptionKey(key []byte) Options { + opt.EncryptionKey = key + return opt +} + +// WithEncryptionKeyRotationDuration returns new Options value with the duration set to +// the given value. +// +// Key Registry will use this duration to create new keys. If the previous generated +// key exceed the given duration. Then the key registry will create new key. +func (opt Options) WithEncryptionKeyRotationDuration(d time.Duration) Options { + opt.EncryptionKeyRotationDuration = d + return opt +} + +// WithCompression is used to enable or disable compression. When compression is enabled, every +// block will be compressed using the specified algorithm. This option doesn't affect existing +// tables. Only the newly created tables will be compressed. +// +// The default compression algorithm used is zstd when built with Cgo. Without Cgo, the default is +// snappy. Compression is enabled by default. +func (opt Options) WithCompression(cType options.CompressionType) Options { + opt.Compression = cType + return opt +} + +// WithVerifyValueChecksum is used to set VerifyValueChecksum. When VerifyValueChecksum is set to +// true, checksum will be verified for every entry read from the value log. If the value is stored +// in SST (value size less than value threshold) then the checksum validation will not be done. +// +// The default value of VerifyValueChecksum is False. +func (opt Options) WithVerifyValueChecksum(val bool) Options { + opt.VerifyValueChecksum = val + return opt +} + +// WithChecksumVerificationMode returns a new Options value with ChecksumVerificationMode set to +// the given value. +// +// ChecksumVerificationMode indicates when the db should verify checksums for SSTable blocks. +// +// The default value of VerifyValueChecksum is options.NoVerification. +func (opt Options) WithChecksumVerificationMode(cvMode options.ChecksumVerificationMode) Options { + opt.ChecksumVerificationMode = cvMode + return opt +} + +// WithBlockCacheSize returns a new Options value with BlockCacheSize set to the given value. +// +// This value specifies how much data cache should hold in memory. A small size +// of cache means lower memory consumption and lookups/iterations would take +// longer. It is recommended to use a cache if you're using compression or encryption. +// If compression and encryption both are disabled, adding a cache will lead to +// unnecessary overhead which will affect the read performance. Setting size to +// zero disables the cache altogether. +// +// Default value of BlockCacheSize is zero. +func (opt Options) WithBlockCacheSize(size int64) Options { + opt.BlockCacheSize = size + return opt +} + +// WithInMemory returns a new Options value with Inmemory mode set to the given value. +// +// When badger is running in InMemory mode, everything is stored in memory. No value/sst files are +// created. In case of a crash all data will be lost. +func (opt Options) WithInMemory(b bool) Options { + opt.InMemory = b + return opt +} + +// WithZSTDCompressionLevel returns a new Options value with ZSTDCompressionLevel set +// to the given value. +// +// The ZSTD compression algorithm supports 20 compression levels. The higher the compression +// level, the better is the compression ratio but lower is the performance. Lower levels +// have better performance and higher levels have better compression ratios. +// We recommend using level 1 ZSTD Compression Level. Any level higher than 1 seems to +// deteriorate badger's performance. +// The following benchmarks were done on a 4 KB block size (default block size). The compression is +// ratio supposed to increase with increasing compression level but since the input for compression +// algorithm is small (4 KB), we don't get significant benefit at level 3. It is advised to write +// your own benchmarks before choosing a compression algorithm or level. +// +// NOTE: The benchmarks are with DataDog ZSTD that requires CGO. Hence, no longer valid. +// no_compression-16 10 502848865 ns/op 165.46 MB/s - +// zstd_compression/level_1-16 7 739037966 ns/op 112.58 MB/s 2.93 +// zstd_compression/level_3-16 7 756950250 ns/op 109.91 MB/s 2.72 +// zstd_compression/level_15-16 1 11135686219 ns/op 7.47 MB/s 4.38 +// Benchmark code can be found in table/builder_test.go file +func (opt Options) WithZSTDCompressionLevel(cLevel int) Options { + opt.ZSTDCompressionLevel = cLevel + return opt +} + +// WithBypassLockGuard returns a new Options value with BypassLockGuard +// set to the given value. +// +// When BypassLockGuard option is set, badger will not acquire a lock on the +// directory. This could lead to data corruption if multiple badger instances +// write to the same data directory. Use this option with caution. +// +// The default value of BypassLockGuard is false. +func (opt Options) WithBypassLockGuard(b bool) Options { + opt.BypassLockGuard = b + return opt +} + +// WithIndexCacheSize returns a new Options value with IndexCacheSize set to +// the given value. +// +// This value specifies how much memory should be used by table indices. These +// indices include the block offsets and the bloomfilters. Badger uses bloom +// filters to speed up lookups. Each table has its own bloom +// filter and each bloom filter is approximately of 5 MB. +// +// Zero value for IndexCacheSize means all the indices will be kept in +// memory and the cache is disabled. +// +// The default value of IndexCacheSize is 0 which means all indices are kept in +// memory. +func (opt Options) WithIndexCacheSize(size int64) Options { + opt.IndexCacheSize = size + return opt +} + +// WithDetectConflicts returns a new Options value with DetectConflicts set to the given value. +// +// Detect conflicts options determines if the transactions would be checked for +// conflicts before committing them. When this option is set to false +// (detectConflicts=false) badger can process transactions at a higher rate. +// Setting this options to false might be useful when the user application +// deals with conflict detection and resolution. +// +// The default value of Detect conflicts is True. +func (opt Options) WithDetectConflicts(b bool) Options { + opt.DetectConflicts = b + return opt +} + +// WithNamespaceOffset returns a new Options value with NamespaceOffset set to the given value. DB +// will expect the namespace in each key at the 8 bytes starting from NamespaceOffset. A negative +// value means that namespace is not stored in the key. +// +// The default value for NamespaceOffset is -1. +func (opt Options) WithNamespaceOffset(offset int) Options { + opt.NamespaceOffset = offset + return opt +} + +func (opt Options) getFileFlags() int { + var flags int + // opt.SyncWrites would be using msync to sync. All writes go through mmap. + if opt.ReadOnly { + flags |= os.O_RDONLY + } else { + flags |= os.O_RDWR + } + return flags +} diff --git a/vendor/github.com/dgraph-io/badger/v3/options/options.go b/vendor/github.com/dgraph-io/badger/v3/options/options.go new file mode 100644 index 0000000000..e5ee932c0c --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/options/options.go @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 options + +// ChecksumVerificationMode tells when should DB verify checksum for SSTable blocks. +type ChecksumVerificationMode int + +const ( + // NoVerification indicates DB should not verify checksum for SSTable blocks. + NoVerification ChecksumVerificationMode = iota + // OnTableRead indicates checksum should be verified while opening SSTtable. + OnTableRead + // OnBlockRead indicates checksum should be verified on every SSTable block read. + OnBlockRead + // OnTableAndBlockRead indicates checksum should be verified + // on SSTable opening and on every block read. + OnTableAndBlockRead +) + +// CompressionType specifies how a block should be compressed. +type CompressionType uint32 + +const ( + // None mode indicates that a block is not compressed. + None CompressionType = 0 + // Snappy mode indicates that a block is compressed using Snappy algorithm. + Snappy CompressionType = 1 + // ZSTD mode indicates that a block is compressed using ZSTD algorithm. + ZSTD CompressionType = 2 +) diff --git a/vendor/github.com/dgraph-io/badger/v3/pb/badgerpb3.pb.go b/vendor/github.com/dgraph-io/badger/v3/pb/badgerpb3.pb.go new file mode 100644 index 0000000000..927878ca03 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/pb/badgerpb3.pb.go @@ -0,0 +1,2164 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: badgerpb3.proto + +package pb + +import ( + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type EncryptionAlgo int32 + +const ( + EncryptionAlgo_aes EncryptionAlgo = 0 +) + +var EncryptionAlgo_name = map[int32]string{ + 0: "aes", +} + +var EncryptionAlgo_value = map[string]int32{ + "aes": 0, +} + +func (x EncryptionAlgo) String() string { + return proto.EnumName(EncryptionAlgo_name, int32(x)) +} + +func (EncryptionAlgo) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{0} +} + +type ManifestChange_Operation int32 + +const ( + ManifestChange_CREATE ManifestChange_Operation = 0 + ManifestChange_DELETE ManifestChange_Operation = 1 +) + +var ManifestChange_Operation_name = map[int32]string{ + 0: "CREATE", + 1: "DELETE", +} + +var ManifestChange_Operation_value = map[string]int32{ + "CREATE": 0, + "DELETE": 1, +} + +func (x ManifestChange_Operation) String() string { + return proto.EnumName(ManifestChange_Operation_name, int32(x)) +} + +func (ManifestChange_Operation) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{3, 0} +} + +type Checksum_Algorithm int32 + +const ( + Checksum_CRC32C Checksum_Algorithm = 0 + Checksum_XXHash64 Checksum_Algorithm = 1 +) + +var Checksum_Algorithm_name = map[int32]string{ + 0: "CRC32C", + 1: "XXHash64", +} + +var Checksum_Algorithm_value = map[string]int32{ + "CRC32C": 0, + "XXHash64": 1, +} + +func (x Checksum_Algorithm) String() string { + return proto.EnumName(Checksum_Algorithm_name, int32(x)) +} + +func (Checksum_Algorithm) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{4, 0} +} + +type KV struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + UserMeta []byte `protobuf:"bytes,3,opt,name=user_meta,json=userMeta,proto3" json:"user_meta,omitempty"` + Version uint64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"` + ExpiresAt uint64 `protobuf:"varint,5,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + Meta []byte `protobuf:"bytes,6,opt,name=meta,proto3" json:"meta,omitempty"` + // Stream id is used to identify which stream the KV came from. + StreamId uint32 `protobuf:"varint,10,opt,name=stream_id,json=streamId,proto3" json:"stream_id,omitempty"` + // Stream done is used to indicate end of stream. + StreamDone bool `protobuf:"varint,11,opt,name=stream_done,json=streamDone,proto3" json:"stream_done,omitempty"` +} + +func (m *KV) Reset() { *m = KV{} } +func (m *KV) String() string { return proto.CompactTextString(m) } +func (*KV) ProtoMessage() {} +func (*KV) Descriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{0} +} +func (m *KV) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *KV) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_KV.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *KV) XXX_Merge(src proto.Message) { + xxx_messageInfo_KV.Merge(m, src) +} +func (m *KV) XXX_Size() int { + return m.Size() +} +func (m *KV) XXX_DiscardUnknown() { + xxx_messageInfo_KV.DiscardUnknown(m) +} + +var xxx_messageInfo_KV proto.InternalMessageInfo + +func (m *KV) GetKey() []byte { + if m != nil { + return m.Key + } + return nil +} + +func (m *KV) GetValue() []byte { + if m != nil { + return m.Value + } + return nil +} + +func (m *KV) GetUserMeta() []byte { + if m != nil { + return m.UserMeta + } + return nil +} + +func (m *KV) GetVersion() uint64 { + if m != nil { + return m.Version + } + return 0 +} + +func (m *KV) GetExpiresAt() uint64 { + if m != nil { + return m.ExpiresAt + } + return 0 +} + +func (m *KV) GetMeta() []byte { + if m != nil { + return m.Meta + } + return nil +} + +func (m *KV) GetStreamId() uint32 { + if m != nil { + return m.StreamId + } + return 0 +} + +func (m *KV) GetStreamDone() bool { + if m != nil { + return m.StreamDone + } + return false +} + +type KVList struct { + Kv []*KV `protobuf:"bytes,1,rep,name=kv,proto3" json:"kv,omitempty"` + // alloc_ref used internally for memory management. + AllocRef uint64 `protobuf:"varint,10,opt,name=alloc_ref,json=allocRef,proto3" json:"alloc_ref,omitempty"` +} + +func (m *KVList) Reset() { *m = KVList{} } +func (m *KVList) String() string { return proto.CompactTextString(m) } +func (*KVList) ProtoMessage() {} +func (*KVList) Descriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{1} +} +func (m *KVList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *KVList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_KVList.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *KVList) XXX_Merge(src proto.Message) { + xxx_messageInfo_KVList.Merge(m, src) +} +func (m *KVList) XXX_Size() int { + return m.Size() +} +func (m *KVList) XXX_DiscardUnknown() { + xxx_messageInfo_KVList.DiscardUnknown(m) +} + +var xxx_messageInfo_KVList proto.InternalMessageInfo + +func (m *KVList) GetKv() []*KV { + if m != nil { + return m.Kv + } + return nil +} + +func (m *KVList) GetAllocRef() uint64 { + if m != nil { + return m.AllocRef + } + return 0 +} + +type ManifestChangeSet struct { + // A set of changes that are applied atomically. + Changes []*ManifestChange `protobuf:"bytes,1,rep,name=changes,proto3" json:"changes,omitempty"` +} + +func (m *ManifestChangeSet) Reset() { *m = ManifestChangeSet{} } +func (m *ManifestChangeSet) String() string { return proto.CompactTextString(m) } +func (*ManifestChangeSet) ProtoMessage() {} +func (*ManifestChangeSet) Descriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{2} +} +func (m *ManifestChangeSet) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ManifestChangeSet) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ManifestChangeSet.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ManifestChangeSet) XXX_Merge(src proto.Message) { + xxx_messageInfo_ManifestChangeSet.Merge(m, src) +} +func (m *ManifestChangeSet) XXX_Size() int { + return m.Size() +} +func (m *ManifestChangeSet) XXX_DiscardUnknown() { + xxx_messageInfo_ManifestChangeSet.DiscardUnknown(m) +} + +var xxx_messageInfo_ManifestChangeSet proto.InternalMessageInfo + +func (m *ManifestChangeSet) GetChanges() []*ManifestChange { + if m != nil { + return m.Changes + } + return nil +} + +type ManifestChange struct { + Id uint64 `protobuf:"varint,1,opt,name=Id,proto3" json:"Id,omitempty"` + Op ManifestChange_Operation `protobuf:"varint,2,opt,name=Op,proto3,enum=badgerpb3.ManifestChange_Operation" json:"Op,omitempty"` + Level uint32 `protobuf:"varint,3,opt,name=Level,proto3" json:"Level,omitempty"` + KeyId uint64 `protobuf:"varint,4,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` + EncryptionAlgo EncryptionAlgo `protobuf:"varint,5,opt,name=encryption_algo,json=encryptionAlgo,proto3,enum=badgerpb3.EncryptionAlgo" json:"encryption_algo,omitempty"` + Compression uint32 `protobuf:"varint,6,opt,name=compression,proto3" json:"compression,omitempty"` +} + +func (m *ManifestChange) Reset() { *m = ManifestChange{} } +func (m *ManifestChange) String() string { return proto.CompactTextString(m) } +func (*ManifestChange) ProtoMessage() {} +func (*ManifestChange) Descriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{3} +} +func (m *ManifestChange) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ManifestChange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ManifestChange.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ManifestChange) XXX_Merge(src proto.Message) { + xxx_messageInfo_ManifestChange.Merge(m, src) +} +func (m *ManifestChange) XXX_Size() int { + return m.Size() +} +func (m *ManifestChange) XXX_DiscardUnknown() { + xxx_messageInfo_ManifestChange.DiscardUnknown(m) +} + +var xxx_messageInfo_ManifestChange proto.InternalMessageInfo + +func (m *ManifestChange) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *ManifestChange) GetOp() ManifestChange_Operation { + if m != nil { + return m.Op + } + return ManifestChange_CREATE +} + +func (m *ManifestChange) GetLevel() uint32 { + if m != nil { + return m.Level + } + return 0 +} + +func (m *ManifestChange) GetKeyId() uint64 { + if m != nil { + return m.KeyId + } + return 0 +} + +func (m *ManifestChange) GetEncryptionAlgo() EncryptionAlgo { + if m != nil { + return m.EncryptionAlgo + } + return EncryptionAlgo_aes +} + +func (m *ManifestChange) GetCompression() uint32 { + if m != nil { + return m.Compression + } + return 0 +} + +type Checksum struct { + Algo Checksum_Algorithm `protobuf:"varint,1,opt,name=algo,proto3,enum=badgerpb3.Checksum_Algorithm" json:"algo,omitempty"` + Sum uint64 `protobuf:"varint,2,opt,name=sum,proto3" json:"sum,omitempty"` +} + +func (m *Checksum) Reset() { *m = Checksum{} } +func (m *Checksum) String() string { return proto.CompactTextString(m) } +func (*Checksum) ProtoMessage() {} +func (*Checksum) Descriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{4} +} +func (m *Checksum) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Checksum) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Checksum.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Checksum) XXX_Merge(src proto.Message) { + xxx_messageInfo_Checksum.Merge(m, src) +} +func (m *Checksum) XXX_Size() int { + return m.Size() +} +func (m *Checksum) XXX_DiscardUnknown() { + xxx_messageInfo_Checksum.DiscardUnknown(m) +} + +var xxx_messageInfo_Checksum proto.InternalMessageInfo + +func (m *Checksum) GetAlgo() Checksum_Algorithm { + if m != nil { + return m.Algo + } + return Checksum_CRC32C +} + +func (m *Checksum) GetSum() uint64 { + if m != nil { + return m.Sum + } + return 0 +} + +type DataKey struct { + KeyId uint64 `protobuf:"varint,1,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + Iv []byte `protobuf:"bytes,3,opt,name=iv,proto3" json:"iv,omitempty"` + CreatedAt int64 `protobuf:"varint,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` +} + +func (m *DataKey) Reset() { *m = DataKey{} } +func (m *DataKey) String() string { return proto.CompactTextString(m) } +func (*DataKey) ProtoMessage() {} +func (*DataKey) Descriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{5} +} +func (m *DataKey) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *DataKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_DataKey.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *DataKey) XXX_Merge(src proto.Message) { + xxx_messageInfo_DataKey.Merge(m, src) +} +func (m *DataKey) XXX_Size() int { + return m.Size() +} +func (m *DataKey) XXX_DiscardUnknown() { + xxx_messageInfo_DataKey.DiscardUnknown(m) +} + +var xxx_messageInfo_DataKey proto.InternalMessageInfo + +func (m *DataKey) GetKeyId() uint64 { + if m != nil { + return m.KeyId + } + return 0 +} + +func (m *DataKey) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func (m *DataKey) GetIv() []byte { + if m != nil { + return m.Iv + } + return nil +} + +func (m *DataKey) GetCreatedAt() int64 { + if m != nil { + return m.CreatedAt + } + return 0 +} + +type Match struct { + Prefix []byte `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"` + IgnoreBytes string `protobuf:"bytes,2,opt,name=ignore_bytes,json=ignoreBytes,proto3" json:"ignore_bytes,omitempty"` +} + +func (m *Match) Reset() { *m = Match{} } +func (m *Match) String() string { return proto.CompactTextString(m) } +func (*Match) ProtoMessage() {} +func (*Match) Descriptor() ([]byte, []int) { + return fileDescriptor_6d729c99bbc38987, []int{6} +} +func (m *Match) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Match) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Match.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Match) XXX_Merge(src proto.Message) { + xxx_messageInfo_Match.Merge(m, src) +} +func (m *Match) XXX_Size() int { + return m.Size() +} +func (m *Match) XXX_DiscardUnknown() { + xxx_messageInfo_Match.DiscardUnknown(m) +} + +var xxx_messageInfo_Match proto.InternalMessageInfo + +func (m *Match) GetPrefix() []byte { + if m != nil { + return m.Prefix + } + return nil +} + +func (m *Match) GetIgnoreBytes() string { + if m != nil { + return m.IgnoreBytes + } + return "" +} + +func init() { + proto.RegisterEnum("badgerpb3.EncryptionAlgo", EncryptionAlgo_name, EncryptionAlgo_value) + proto.RegisterEnum("badgerpb3.ManifestChange_Operation", ManifestChange_Operation_name, ManifestChange_Operation_value) + proto.RegisterEnum("badgerpb3.Checksum_Algorithm", Checksum_Algorithm_name, Checksum_Algorithm_value) + proto.RegisterType((*KV)(nil), "badgerpb3.KV") + proto.RegisterType((*KVList)(nil), "badgerpb3.KVList") + proto.RegisterType((*ManifestChangeSet)(nil), "badgerpb3.ManifestChangeSet") + proto.RegisterType((*ManifestChange)(nil), "badgerpb3.ManifestChange") + proto.RegisterType((*Checksum)(nil), "badgerpb3.Checksum") + proto.RegisterType((*DataKey)(nil), "badgerpb3.DataKey") + proto.RegisterType((*Match)(nil), "badgerpb3.Match") +} + +func init() { proto.RegisterFile("badgerpb3.proto", fileDescriptor_6d729c99bbc38987) } + +var fileDescriptor_6d729c99bbc38987 = []byte{ + // 651 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0x4d, 0x6b, 0xdb, 0x40, + 0x10, 0xf5, 0xca, 0xf2, 0xd7, 0x38, 0x71, 0xdc, 0xa5, 0x2d, 0x0a, 0x25, 0xae, 0xa3, 0x50, 0x30, + 0x85, 0xda, 0x34, 0x2e, 0xbd, 0xf4, 0xe4, 0x2f, 0x88, 0x71, 0x42, 0x60, 0x1b, 0x42, 0xe8, 0xc5, + 0xac, 0xa5, 0xb1, 0x2d, 0x6c, 0x4b, 0x62, 0xb5, 0x16, 0xf1, 0x8f, 0x28, 0xf4, 0x67, 0xf5, 0x98, + 0x43, 0x0f, 0x3d, 0x96, 0xe4, 0x8f, 0x94, 0x5d, 0x29, 0xae, 0x7d, 0xe8, 0x6d, 0xe6, 0xcd, 0x68, + 0xde, 0xe8, 0xbd, 0x91, 0xe0, 0x68, 0xc2, 0xdd, 0x19, 0x8a, 0x70, 0xd2, 0x6e, 0x86, 0x22, 0x90, + 0x01, 0x2d, 0x6d, 0x01, 0xfb, 0x17, 0x01, 0x63, 0x74, 0x4b, 0xab, 0x90, 0x5d, 0xe0, 0xc6, 0x22, + 0x75, 0xd2, 0x38, 0x60, 0x2a, 0xa4, 0x2f, 0x21, 0x17, 0xf3, 0xe5, 0x1a, 0x2d, 0x43, 0x63, 0x49, + 0x42, 0xdf, 0x40, 0x69, 0x1d, 0xa1, 0x18, 0xaf, 0x50, 0x72, 0x2b, 0xab, 0x2b, 0x45, 0x05, 0x5c, + 0xa1, 0xe4, 0xd4, 0x82, 0x42, 0x8c, 0x22, 0xf2, 0x02, 0xdf, 0x32, 0xeb, 0xa4, 0x61, 0xb2, 0xe7, + 0x94, 0x9e, 0x00, 0xe0, 0x7d, 0xe8, 0x09, 0x8c, 0xc6, 0x5c, 0x5a, 0x39, 0x5d, 0x2c, 0xa5, 0x48, + 0x47, 0x52, 0x0a, 0xa6, 0x1e, 0x98, 0xd7, 0x03, 0x75, 0xac, 0x98, 0x22, 0x29, 0x90, 0xaf, 0xc6, + 0x9e, 0x6b, 0x41, 0x9d, 0x34, 0x0e, 0x59, 0x31, 0x01, 0x86, 0x2e, 0x7d, 0x0b, 0xe5, 0xb4, 0xe8, + 0x06, 0x3e, 0x5a, 0xe5, 0x3a, 0x69, 0x14, 0x19, 0x24, 0x50, 0x3f, 0xf0, 0xd1, 0xee, 0x43, 0x7e, + 0x74, 0x7b, 0xe9, 0x45, 0x92, 0x9e, 0x80, 0xb1, 0x88, 0x2d, 0x52, 0xcf, 0x36, 0xca, 0xe7, 0x87, + 0xcd, 0x7f, 0x4a, 0x8c, 0x6e, 0x99, 0xb1, 0x88, 0x15, 0x0d, 0x5f, 0x2e, 0x03, 0x67, 0x2c, 0x70, + 0xaa, 0x69, 0x4c, 0x56, 0xd4, 0x00, 0xc3, 0xa9, 0x7d, 0x01, 0x2f, 0xae, 0xb8, 0xef, 0x4d, 0x31, + 0x92, 0xbd, 0x39, 0xf7, 0x67, 0xf8, 0x15, 0x25, 0x6d, 0x43, 0xc1, 0xd1, 0x49, 0x94, 0x4e, 0x3d, + 0xde, 0x99, 0xba, 0xdf, 0xce, 0x9e, 0x3b, 0xed, 0xef, 0x06, 0x54, 0xf6, 0x6b, 0xb4, 0x02, 0xc6, + 0xd0, 0xd5, 0x8a, 0x9b, 0xcc, 0x18, 0xba, 0xb4, 0x0d, 0xc6, 0x75, 0xa8, 0xd5, 0xae, 0x9c, 0x9f, + 0xfd, 0x77, 0x64, 0xf3, 0x3a, 0x44, 0xc1, 0xa5, 0x17, 0xf8, 0xcc, 0xb8, 0x0e, 0x95, 0x4b, 0x97, + 0x18, 0xe3, 0x52, 0x7b, 0x71, 0xc8, 0x92, 0x84, 0xbe, 0x82, 0xfc, 0x02, 0x37, 0x4a, 0xb8, 0xc4, + 0x87, 0xdc, 0x02, 0x37, 0x43, 0x97, 0x76, 0xe1, 0x08, 0x7d, 0x47, 0x6c, 0x42, 0xf5, 0xf8, 0x98, + 0x2f, 0x67, 0x81, 0xb6, 0xa2, 0xb2, 0xf7, 0x06, 0x83, 0x6d, 0x47, 0x67, 0x39, 0x0b, 0x58, 0x05, + 0xf7, 0x72, 0x5a, 0x87, 0xb2, 0x13, 0xac, 0x42, 0x81, 0x91, 0xf6, 0x39, 0xaf, 0x69, 0x77, 0x21, + 0xfb, 0x0c, 0x4a, 0xdb, 0x1d, 0x29, 0x40, 0xbe, 0xc7, 0x06, 0x9d, 0x9b, 0x41, 0x35, 0xa3, 0xe2, + 0xfe, 0xe0, 0x72, 0x70, 0x33, 0xa8, 0x12, 0x3b, 0x86, 0x62, 0x6f, 0x8e, 0xce, 0x22, 0x5a, 0xaf, + 0xe8, 0x47, 0x30, 0xf5, 0x2e, 0x44, 0xef, 0x72, 0xb2, 0xb3, 0xcb, 0x73, 0x4b, 0x53, 0x51, 0x0b, + 0x4f, 0xce, 0x57, 0x4c, 0xb7, 0xaa, 0x73, 0x8d, 0xd6, 0x2b, 0x2d, 0x96, 0xc9, 0x54, 0x68, 0xbf, + 0x83, 0xd2, 0xb6, 0x29, 0x61, 0xed, 0xb5, 0xcf, 0x7b, 0xd5, 0x0c, 0x3d, 0x80, 0xe2, 0xdd, 0xdd, + 0x05, 0x8f, 0xe6, 0x9f, 0x3f, 0x55, 0x89, 0xed, 0x40, 0xa1, 0xcf, 0x25, 0x1f, 0xe1, 0x66, 0x47, + 0x24, 0xb2, 0x2b, 0x12, 0x05, 0xd3, 0xe5, 0x92, 0xa7, 0x67, 0xaf, 0x63, 0x65, 0x95, 0x17, 0xa7, + 0xe7, 0x6e, 0x78, 0xb1, 0x3a, 0x67, 0x47, 0x20, 0x97, 0xe8, 0xaa, 0x73, 0x56, 0x1a, 0x67, 0x59, + 0x29, 0x45, 0x3a, 0xd2, 0xee, 0x42, 0xee, 0x8a, 0x4b, 0x67, 0x4e, 0x5f, 0x43, 0x3e, 0x14, 0x38, + 0xf5, 0xee, 0xd3, 0x0f, 0x2b, 0xcd, 0xe8, 0x29, 0x1c, 0x78, 0x33, 0x3f, 0x10, 0x38, 0x9e, 0x6c, + 0x24, 0x46, 0x9a, 0xab, 0xc4, 0xca, 0x09, 0xd6, 0x55, 0xd0, 0xfb, 0x63, 0xa8, 0xec, 0x3b, 0x41, + 0x0b, 0x90, 0xe5, 0x18, 0x55, 0x33, 0xdd, 0x2f, 0x3f, 0x1f, 0x6b, 0xe4, 0xe1, 0xb1, 0x46, 0xfe, + 0x3c, 0xd6, 0xc8, 0x8f, 0xa7, 0x5a, 0xe6, 0xe1, 0xa9, 0x96, 0xf9, 0xfd, 0x54, 0xcb, 0x7c, 0x3b, + 0x9d, 0x79, 0x72, 0xbe, 0x9e, 0x34, 0x9d, 0x60, 0xd5, 0x72, 0x67, 0x82, 0x87, 0xf3, 0x0f, 0x5e, + 0xd0, 0x4a, 0xf4, 0x6c, 0xc5, 0xed, 0x56, 0x38, 0x99, 0xe4, 0xf5, 0x1f, 0xa0, 0xfd, 0x37, 0x00, + 0x00, 0xff, 0xff, 0xa2, 0x8d, 0xa8, 0xf5, 0x14, 0x04, 0x00, 0x00, +} + +func (m *KV) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *KV) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *KV) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.StreamDone { + i-- + if m.StreamDone { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x58 + } + if m.StreamId != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.StreamId)) + i-- + dAtA[i] = 0x50 + } + if len(m.Meta) > 0 { + i -= len(m.Meta) + copy(dAtA[i:], m.Meta) + i = encodeVarintBadgerpb3(dAtA, i, uint64(len(m.Meta))) + i-- + dAtA[i] = 0x32 + } + if m.ExpiresAt != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.ExpiresAt)) + i-- + dAtA[i] = 0x28 + } + if m.Version != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.Version)) + i-- + dAtA[i] = 0x20 + } + if len(m.UserMeta) > 0 { + i -= len(m.UserMeta) + copy(dAtA[i:], m.UserMeta) + i = encodeVarintBadgerpb3(dAtA, i, uint64(len(m.UserMeta))) + i-- + dAtA[i] = 0x1a + } + if len(m.Value) > 0 { + i -= len(m.Value) + copy(dAtA[i:], m.Value) + i = encodeVarintBadgerpb3(dAtA, i, uint64(len(m.Value))) + i-- + dAtA[i] = 0x12 + } + if len(m.Key) > 0 { + i -= len(m.Key) + copy(dAtA[i:], m.Key) + i = encodeVarintBadgerpb3(dAtA, i, uint64(len(m.Key))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *KVList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *KVList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *KVList) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.AllocRef != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.AllocRef)) + i-- + dAtA[i] = 0x50 + } + if len(m.Kv) > 0 { + for iNdEx := len(m.Kv) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Kv[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBadgerpb3(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ManifestChangeSet) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ManifestChangeSet) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ManifestChangeSet) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Changes) > 0 { + for iNdEx := len(m.Changes) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Changes[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintBadgerpb3(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ManifestChange) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ManifestChange) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ManifestChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Compression != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.Compression)) + i-- + dAtA[i] = 0x30 + } + if m.EncryptionAlgo != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.EncryptionAlgo)) + i-- + dAtA[i] = 0x28 + } + if m.KeyId != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.KeyId)) + i-- + dAtA[i] = 0x20 + } + if m.Level != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.Level)) + i-- + dAtA[i] = 0x18 + } + if m.Op != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.Op)) + i-- + dAtA[i] = 0x10 + } + if m.Id != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.Id)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Checksum) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Checksum) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Checksum) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sum != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.Sum)) + i-- + dAtA[i] = 0x10 + } + if m.Algo != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.Algo)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *DataKey) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *DataKey) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *DataKey) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.CreatedAt != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.CreatedAt)) + i-- + dAtA[i] = 0x20 + } + if len(m.Iv) > 0 { + i -= len(m.Iv) + copy(dAtA[i:], m.Iv) + i = encodeVarintBadgerpb3(dAtA, i, uint64(len(m.Iv))) + i-- + dAtA[i] = 0x1a + } + if len(m.Data) > 0 { + i -= len(m.Data) + copy(dAtA[i:], m.Data) + i = encodeVarintBadgerpb3(dAtA, i, uint64(len(m.Data))) + i-- + dAtA[i] = 0x12 + } + if m.KeyId != 0 { + i = encodeVarintBadgerpb3(dAtA, i, uint64(m.KeyId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Match) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Match) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Match) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.IgnoreBytes) > 0 { + i -= len(m.IgnoreBytes) + copy(dAtA[i:], m.IgnoreBytes) + i = encodeVarintBadgerpb3(dAtA, i, uint64(len(m.IgnoreBytes))) + i-- + dAtA[i] = 0x12 + } + if len(m.Prefix) > 0 { + i -= len(m.Prefix) + copy(dAtA[i:], m.Prefix) + i = encodeVarintBadgerpb3(dAtA, i, uint64(len(m.Prefix))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintBadgerpb3(dAtA []byte, offset int, v uint64) int { + offset -= sovBadgerpb3(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *KV) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Key) + if l > 0 { + n += 1 + l + sovBadgerpb3(uint64(l)) + } + l = len(m.Value) + if l > 0 { + n += 1 + l + sovBadgerpb3(uint64(l)) + } + l = len(m.UserMeta) + if l > 0 { + n += 1 + l + sovBadgerpb3(uint64(l)) + } + if m.Version != 0 { + n += 1 + sovBadgerpb3(uint64(m.Version)) + } + if m.ExpiresAt != 0 { + n += 1 + sovBadgerpb3(uint64(m.ExpiresAt)) + } + l = len(m.Meta) + if l > 0 { + n += 1 + l + sovBadgerpb3(uint64(l)) + } + if m.StreamId != 0 { + n += 1 + sovBadgerpb3(uint64(m.StreamId)) + } + if m.StreamDone { + n += 2 + } + return n +} + +func (m *KVList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Kv) > 0 { + for _, e := range m.Kv { + l = e.Size() + n += 1 + l + sovBadgerpb3(uint64(l)) + } + } + if m.AllocRef != 0 { + n += 1 + sovBadgerpb3(uint64(m.AllocRef)) + } + return n +} + +func (m *ManifestChangeSet) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Changes) > 0 { + for _, e := range m.Changes { + l = e.Size() + n += 1 + l + sovBadgerpb3(uint64(l)) + } + } + return n +} + +func (m *ManifestChange) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Id != 0 { + n += 1 + sovBadgerpb3(uint64(m.Id)) + } + if m.Op != 0 { + n += 1 + sovBadgerpb3(uint64(m.Op)) + } + if m.Level != 0 { + n += 1 + sovBadgerpb3(uint64(m.Level)) + } + if m.KeyId != 0 { + n += 1 + sovBadgerpb3(uint64(m.KeyId)) + } + if m.EncryptionAlgo != 0 { + n += 1 + sovBadgerpb3(uint64(m.EncryptionAlgo)) + } + if m.Compression != 0 { + n += 1 + sovBadgerpb3(uint64(m.Compression)) + } + return n +} + +func (m *Checksum) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Algo != 0 { + n += 1 + sovBadgerpb3(uint64(m.Algo)) + } + if m.Sum != 0 { + n += 1 + sovBadgerpb3(uint64(m.Sum)) + } + return n +} + +func (m *DataKey) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.KeyId != 0 { + n += 1 + sovBadgerpb3(uint64(m.KeyId)) + } + l = len(m.Data) + if l > 0 { + n += 1 + l + sovBadgerpb3(uint64(l)) + } + l = len(m.Iv) + if l > 0 { + n += 1 + l + sovBadgerpb3(uint64(l)) + } + if m.CreatedAt != 0 { + n += 1 + sovBadgerpb3(uint64(m.CreatedAt)) + } + return n +} + +func (m *Match) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Prefix) + if l > 0 { + n += 1 + l + sovBadgerpb3(uint64(l)) + } + l = len(m.IgnoreBytes) + if l > 0 { + n += 1 + l + sovBadgerpb3(uint64(l)) + } + return n +} + +func sovBadgerpb3(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozBadgerpb3(x uint64) (n int) { + return sovBadgerpb3(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *KV) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: KV: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: KV: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...) + if m.Key == nil { + m.Key = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...) + if m.Value == nil { + m.Value = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserMeta", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UserMeta = append(m.UserMeta[:0], dAtA[iNdEx:postIndex]...) + if m.UserMeta == nil { + m.UserMeta = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + m.Version = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Version |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpiresAt", wireType) + } + m.ExpiresAt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ExpiresAt |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Meta", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Meta = append(m.Meta[:0], dAtA[iNdEx:postIndex]...) + if m.Meta == nil { + m.Meta = []byte{} + } + iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StreamId", wireType) + } + m.StreamId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StreamId |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StreamDone", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.StreamDone = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipBadgerpb3(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBadgerpb3 + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *KVList) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: KVList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: KVList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Kv", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Kv = append(m.Kv, &KV{}) + if err := m.Kv[len(m.Kv)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocRef", wireType) + } + m.AllocRef = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AllocRef |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBadgerpb3(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBadgerpb3 + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ManifestChangeSet) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ManifestChangeSet: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ManifestChangeSet: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Changes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Changes = append(m.Changes, &ManifestChange{}) + if err := m.Changes[len(m.Changes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBadgerpb3(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBadgerpb3 + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ManifestChange) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ManifestChange: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ManifestChange: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + m.Id = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Id |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Op", wireType) + } + m.Op = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Op |= ManifestChange_Operation(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Level", wireType) + } + m.Level = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Level |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field KeyId", wireType) + } + m.KeyId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.KeyId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EncryptionAlgo", wireType) + } + m.EncryptionAlgo = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EncryptionAlgo |= EncryptionAlgo(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Compression", wireType) + } + m.Compression = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Compression |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBadgerpb3(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBadgerpb3 + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Checksum) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Checksum: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Checksum: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Algo", wireType) + } + m.Algo = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Algo |= Checksum_Algorithm(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Sum", wireType) + } + m.Sum = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Sum |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBadgerpb3(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBadgerpb3 + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DataKey) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataKey: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataKey: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field KeyId", wireType) + } + m.KeyId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.KeyId |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Iv", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Iv = append(m.Iv[:0], dAtA[iNdEx:postIndex]...) + if m.Iv == nil { + m.Iv = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CreatedAt", wireType) + } + m.CreatedAt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CreatedAt |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBadgerpb3(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBadgerpb3 + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Match) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Match: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Match: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prefix", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Prefix = append(m.Prefix[:0], dAtA[iNdEx:postIndex]...) + if m.Prefix == nil { + m.Prefix = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field IgnoreBytes", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBadgerpb3 + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthBadgerpb3 + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.IgnoreBytes = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBadgerpb3(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthBadgerpb3 + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipBadgerpb3(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBadgerpb3 + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthBadgerpb3 + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupBadgerpb3 + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthBadgerpb3 + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthBadgerpb3 = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowBadgerpb3 = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupBadgerpb3 = fmt.Errorf("proto: unexpected end of group") +) diff --git a/vendor/github.com/dgraph-io/badger/v3/pb/badgerpb3.proto b/vendor/github.com/dgraph-io/badger/v3/pb/badgerpb3.proto new file mode 100644 index 0000000000..d0df21fb33 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/pb/badgerpb3.proto @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 Dgraph Labs, Inc. and Contributors + * + * 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. + */ + +// Use protos/gen.sh to generate .pb.go files. +syntax = "proto3"; + +package badgerpb3; + +option go_package = "github.com/dgraph-io/badger/v3/pb"; + +message KV { + bytes key = 1; + bytes value = 2; + bytes user_meta = 3; + uint64 version = 4; + uint64 expires_at = 5; + bytes meta = 6; + + // Stream id is used to identify which stream the KV came from. + uint32 stream_id = 10; + // Stream done is used to indicate end of stream. + bool stream_done = 11; +} + +message KVList { + repeated KV kv = 1; + + // alloc_ref used internally for memory management. + uint64 alloc_ref = 10; +} + +message ManifestChangeSet { + // A set of changes that are applied atomically. + repeated ManifestChange changes = 1; +} + +enum EncryptionAlgo { + aes = 0; +} + +message ManifestChange { + uint64 Id = 1; // Table ID. + enum Operation { + CREATE = 0; + DELETE = 1; + } + Operation Op = 2; + uint32 Level = 3; // Only used for CREATE. + uint64 key_id = 4; + EncryptionAlgo encryption_algo = 5; + uint32 compression = 6; // Only used for CREATE Op. +} + +message Checksum { + enum Algorithm { + CRC32C = 0; + XXHash64 = 1; + } + Algorithm algo = 1; // For storing type of Checksum algorithm used + uint64 sum = 2; +} + +message DataKey { + uint64 key_id = 1; + bytes data = 2; + bytes iv = 3; + int64 created_at = 4; +} + +message Match { + bytes prefix = 1; + string ignore_bytes = 2; // Comma separated with dash to represent ranges "1, 2-3, 4-7, 9" +} diff --git a/vendor/github.com/dgraph-io/badger/v3/pb/gen.sh b/vendor/github.com/dgraph-io/badger/v3/pb/gen.sh new file mode 100644 index 0000000000..e41208c9da --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/pb/gen.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# Run this script from its directory, so that badgerpb2.proto is where it's expected to +# be. + +# You might need to go get -v github.com/gogo/protobuf/... +go get -v github.com/gogo/protobuf/protoc-gen-gogofaster +protoc --gogofaster_out=. --gogofaster_opt=paths=source_relative -I=. badgerpb3.proto diff --git a/vendor/github.com/dgraph-io/badger/v3/publisher.go b/vendor/github.com/dgraph-io/badger/v3/publisher.go new file mode 100644 index 0000000000..f4c31b2ea6 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/publisher.go @@ -0,0 +1,177 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "sync" + "sync/atomic" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/trie" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" +) + +type subscriber struct { + id uint64 + matches []pb.Match + sendCh chan *pb.KVList + subCloser *z.Closer + // this will be atomic pointer which will be used to + // track whether the subscriber is active or not + active *uint64 +} + +type publisher struct { + sync.Mutex + pubCh chan requests + subscribers map[uint64]subscriber + nextID uint64 + indexer *trie.Trie +} + +func newPublisher() *publisher { + return &publisher{ + pubCh: make(chan requests, 1000), + subscribers: make(map[uint64]subscriber), + nextID: 0, + indexer: trie.NewTrie(), + } +} + +func (p *publisher) listenForUpdates(c *z.Closer) { + defer func() { + p.cleanSubscribers() + c.Done() + }() + slurp := func(batch requests) { + for { + select { + case reqs := <-p.pubCh: + batch = append(batch, reqs...) + default: + p.publishUpdates(batch) + return + } + } + } + for { + select { + case <-c.HasBeenClosed(): + return + case reqs := <-p.pubCh: + slurp(reqs) + } + } +} + +func (p *publisher) publishUpdates(reqs requests) { + p.Lock() + defer func() { + p.Unlock() + // Release all the request. + reqs.DecrRef() + }() + batchedUpdates := make(map[uint64]*pb.KVList) + for _, req := range reqs { + for _, e := range req.Entries { + ids := p.indexer.Get(e.Key) + if len(ids) == 0 { + continue + } + k := y.SafeCopy(nil, e.Key) + kv := &pb.KV{ + Key: y.ParseKey(k), + Value: y.SafeCopy(nil, e.Value), + Meta: []byte{e.UserMeta}, + ExpiresAt: e.ExpiresAt, + Version: y.ParseTs(k), + } + for id := range ids { + if _, ok := batchedUpdates[id]; !ok { + batchedUpdates[id] = &pb.KVList{} + } + batchedUpdates[id].Kv = append(batchedUpdates[id].Kv, kv) + } + } + } + + for id, kvs := range batchedUpdates { + if atomic.LoadUint64(p.subscribers[id].active) == 1 { + p.subscribers[id].sendCh <- kvs + } + } +} + +func (p *publisher) newSubscriber(c *z.Closer, matches []pb.Match) subscriber { + p.Lock() + defer p.Unlock() + ch := make(chan *pb.KVList, 1000) + id := p.nextID + // Increment next ID. + p.nextID++ + active := uint64(1) + s := subscriber{ + active: &active, + id: id, + matches: matches, + sendCh: ch, + subCloser: c, + } + p.subscribers[id] = s + for _, m := range matches { + p.indexer.AddMatch(m, id) + } + return s +} + +// cleanSubscribers stops all the subscribers. Ideally, It should be called while closing DB. +func (p *publisher) cleanSubscribers() { + p.Lock() + defer p.Unlock() + for id, s := range p.subscribers { + for _, m := range s.matches { + p.indexer.DeleteMatch(m, id) + } + delete(p.subscribers, id) + s.subCloser.SignalAndWait() + } +} + +func (p *publisher) deleteSubscriber(id uint64) { + p.Lock() + defer p.Unlock() + if s, ok := p.subscribers[id]; ok { + for _, m := range s.matches { + p.indexer.DeleteMatch(m, id) + } + } + delete(p.subscribers, id) +} + +func (p *publisher) sendUpdates(reqs requests) { + if p.noOfSubscribers() != 0 { + reqs.IncrRef() + p.pubCh <- reqs + } +} + +func (p *publisher) noOfSubscribers() int { + p.Lock() + defer p.Unlock() + return len(p.subscribers) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/skl/README.md b/vendor/github.com/dgraph-io/badger/v3/skl/README.md new file mode 100644 index 0000000000..e22e4590bb --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/skl/README.md @@ -0,0 +1,113 @@ +This is much better than `skiplist` and `slist`. + +``` +BenchmarkReadWrite/frac_0-8 3000000 537 ns/op +BenchmarkReadWrite/frac_1-8 3000000 503 ns/op +BenchmarkReadWrite/frac_2-8 3000000 492 ns/op +BenchmarkReadWrite/frac_3-8 3000000 475 ns/op +BenchmarkReadWrite/frac_4-8 3000000 440 ns/op +BenchmarkReadWrite/frac_5-8 5000000 442 ns/op +BenchmarkReadWrite/frac_6-8 5000000 380 ns/op +BenchmarkReadWrite/frac_7-8 5000000 338 ns/op +BenchmarkReadWrite/frac_8-8 5000000 294 ns/op +BenchmarkReadWrite/frac_9-8 10000000 268 ns/op +BenchmarkReadWrite/frac_10-8 100000000 26.3 ns/op +``` + +And even better than a simple map with read-write lock: + +``` +BenchmarkReadWriteMap/frac_0-8 2000000 774 ns/op +BenchmarkReadWriteMap/frac_1-8 2000000 647 ns/op +BenchmarkReadWriteMap/frac_2-8 3000000 605 ns/op +BenchmarkReadWriteMap/frac_3-8 3000000 603 ns/op +BenchmarkReadWriteMap/frac_4-8 3000000 556 ns/op +BenchmarkReadWriteMap/frac_5-8 3000000 472 ns/op +BenchmarkReadWriteMap/frac_6-8 3000000 476 ns/op +BenchmarkReadWriteMap/frac_7-8 3000000 457 ns/op +BenchmarkReadWriteMap/frac_8-8 5000000 444 ns/op +BenchmarkReadWriteMap/frac_9-8 5000000 361 ns/op +BenchmarkReadWriteMap/frac_10-8 10000000 212 ns/op +``` + +# Node Pooling + +Command used + +``` +rm -Rf tmp && /usr/bin/time -l ./populate -keys_mil 10 +``` + +For pprof results, we run without using /usr/bin/time. There are four runs below. + +Results seem to vary quite a bit between runs. + +## Before node pooling + +``` +1311.53MB of 1338.69MB total (97.97%) +Dropped 30 nodes (cum <= 6.69MB) +Showing top 10 nodes out of 37 (cum >= 12.50MB) + flat flat% sum% cum cum% + 523.04MB 39.07% 39.07% 523.04MB 39.07% github.com/dgraph-io/badger/skl.(*Skiplist).Put + 184.51MB 13.78% 52.85% 184.51MB 13.78% runtime.stringtoslicebyte + 166.01MB 12.40% 65.25% 689.04MB 51.47% github.com/dgraph-io/badger/mem.(*Table).Put + 165MB 12.33% 77.58% 165MB 12.33% runtime.convT2E + 116.92MB 8.73% 86.31% 116.92MB 8.73% bytes.makeSlice + 62.50MB 4.67% 90.98% 62.50MB 4.67% main.newValue + 34.50MB 2.58% 93.56% 34.50MB 2.58% github.com/dgraph-io/badger/table.(*BlockIterator).parseKV + 25.50MB 1.90% 95.46% 100.06MB 7.47% github.com/dgraph-io/badger/y.(*MergeIterator).Next + 21.06MB 1.57% 97.04% 21.06MB 1.57% github.com/dgraph-io/badger/table.(*Table).read + 12.50MB 0.93% 97.97% 12.50MB 0.93% github.com/dgraph-io/badger/table.header.Encode + + 128.31 real 329.37 user 17.11 sys +3355660288 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 2203080 page reclaims + 764 page faults + 0 swaps + 275 block input operations + 76 block output operations + 0 messages sent + 0 messages received + 0 signals received + 49173 voluntary context switches + 599922 involuntary context switches +``` + +## After node pooling + +``` +1963.13MB of 2026.09MB total (96.89%) +Dropped 29 nodes (cum <= 10.13MB) +Showing top 10 nodes out of 41 (cum >= 185.62MB) + flat flat% sum% cum cum% + 658.05MB 32.48% 32.48% 658.05MB 32.48% github.com/dgraph-io/badger/skl.glob..func1 + 297.51MB 14.68% 47.16% 297.51MB 14.68% runtime.convT2E + 257.51MB 12.71% 59.87% 257.51MB 12.71% runtime.stringtoslicebyte + 249.01MB 12.29% 72.16% 1007.06MB 49.70% github.com/dgraph-io/badger/mem.(*Table).Put + 142.43MB 7.03% 79.19% 142.43MB 7.03% bytes.makeSlice + 100MB 4.94% 84.13% 758.05MB 37.41% github.com/dgraph-io/badger/skl.newNode + 99.50MB 4.91% 89.04% 99.50MB 4.91% main.newValue + 75MB 3.70% 92.74% 75MB 3.70% github.com/dgraph-io/badger/table.(*BlockIterator).parseKV + 44.62MB 2.20% 94.94% 44.62MB 2.20% github.com/dgraph-io/badger/table.(*Table).read + 39.50MB 1.95% 96.89% 185.62MB 9.16% github.com/dgraph-io/badger/y.(*MergeIterator).Next + + 135.58 real 374.29 user 17.65 sys +3740614656 maximum resident set size + 0 average shared memory size + 0 average unshared data size + 0 average unshared stack size + 2276566 page reclaims + 770 page faults + 0 swaps + 128 block input operations + 90 block output operations + 0 messages sent + 0 messages received + 0 signals received + 46434 voluntary context switches + 597049 involuntary context switches +``` diff --git a/vendor/github.com/dgraph-io/badger/v3/skl/arena.go b/vendor/github.com/dgraph-io/badger/v3/skl/arena.go new file mode 100644 index 0000000000..facf07325d --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/skl/arena.go @@ -0,0 +1,135 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 skl + +import ( + "sync/atomic" + "unsafe" + + "github.com/dgraph-io/badger/v3/y" +) + +const ( + offsetSize = int(unsafe.Sizeof(uint32(0))) + + // Always align nodes on 64-bit boundaries, even on 32-bit architectures, + // so that the node.value field is 64-bit aligned. This is necessary because + // node.getValueOffset uses atomic.LoadUint64, which expects its input + // pointer to be 64-bit aligned. + nodeAlign = int(unsafe.Sizeof(uint64(0))) - 1 +) + +// Arena should be lock-free. +type Arena struct { + n uint32 + buf []byte +} + +// newArena returns a new arena. +func newArena(n int64) *Arena { + // Don't store data at position 0 in order to reserve offset=0 as a kind + // of nil pointer. + out := &Arena{ + n: 1, + buf: make([]byte, n), + } + return out +} + +func (s *Arena) size() int64 { + return int64(atomic.LoadUint32(&s.n)) +} + +// putNode allocates a node in the arena. The node is aligned on a pointer-sized +// boundary. The arena offset of the node is returned. +func (s *Arena) putNode(height int) uint32 { + // Compute the amount of the tower that will never be used, since the height + // is less than maxHeight. + unusedSize := (maxHeight - height) * offsetSize + + // Pad the allocation with enough bytes to ensure pointer alignment. + l := uint32(MaxNodeSize - unusedSize + nodeAlign) + n := atomic.AddUint32(&s.n, l) + y.AssertTruef(int(n) <= len(s.buf), + "Arena too small, toWrite:%d newTotal:%d limit:%d", + l, n, len(s.buf)) + + // Return the aligned offset. + m := (n - l + uint32(nodeAlign)) & ^uint32(nodeAlign) + return m +} + +// Put will *copy* val into arena. To make better use of this, reuse your input +// val buffer. Returns an offset into buf. User is responsible for remembering +// size of val. We could also store this size inside arena but the encoding and +// decoding will incur some overhead. +func (s *Arena) putVal(v y.ValueStruct) uint32 { + l := uint32(v.EncodedSize()) + n := atomic.AddUint32(&s.n, l) + y.AssertTruef(int(n) <= len(s.buf), + "Arena too small, toWrite:%d newTotal:%d limit:%d", + l, n, len(s.buf)) + m := n - l + v.Encode(s.buf[m:]) + return m +} + +func (s *Arena) putKey(key []byte) uint32 { + l := uint32(len(key)) + n := atomic.AddUint32(&s.n, l) + y.AssertTruef(int(n) <= len(s.buf), + "Arena too small, toWrite:%d newTotal:%d limit:%d", + l, n, len(s.buf)) + // m is the offset where you should write. + // n = new len - key len give you the offset at which you should write. + m := n - l + // Copy to buffer from m:n + y.AssertTrue(len(key) == copy(s.buf[m:n], key)) + return m +} + +// getNode returns a pointer to the node located at offset. If the offset is +// zero, then the nil node pointer is returned. +func (s *Arena) getNode(offset uint32) *node { + if offset == 0 { + return nil + } + + return (*node)(unsafe.Pointer(&s.buf[offset])) +} + +// getKey returns byte slice at offset. +func (s *Arena) getKey(offset uint32, size uint16) []byte { + return s.buf[offset : offset+uint32(size)] +} + +// getVal returns byte slice at offset. The given size should be just the value +// size and should NOT include the meta bytes. +func (s *Arena) getVal(offset uint32, size uint32) (ret y.ValueStruct) { + ret.Decode(s.buf[offset : offset+size]) + return +} + +// getNodeOffset returns the offset of node in the arena. If the node pointer is +// nil, then the zero offset is returned. +func (s *Arena) getNodeOffset(nd *node) uint32 { + if nd == nil { + return 0 + } + + return uint32(uintptr(unsafe.Pointer(nd)) - uintptr(unsafe.Pointer(&s.buf[0]))) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/skl/skl.go b/vendor/github.com/dgraph-io/badger/v3/skl/skl.go new file mode 100644 index 0000000000..a39d349210 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/skl/skl.go @@ -0,0 +1,524 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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. + */ + +/* +Adapted from RocksDB inline skiplist. + +Key differences: +- No optimization for sequential inserts (no "prev"). +- No custom comparator. +- Support overwrites. This requires care when we see the same key when inserting. + For RocksDB or LevelDB, overwrites are implemented as a newer sequence number in the key, so + there is no need for values. We don't intend to support versioning. In-place updates of values + would be more efficient. +- We discard all non-concurrent code. +- We do not support Splices. This simplifies the code a lot. +- No AllocateNode or other pointer arithmetic. +- We combine the findLessThan, findGreaterOrEqual, etc into one function. +*/ + +package skl + +import ( + "math" + "sync/atomic" + "unsafe" + + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" +) + +const ( + maxHeight = 20 + heightIncrease = math.MaxUint32 / 3 +) + +// MaxNodeSize is the memory footprint of a node of maximum height. +const MaxNodeSize = int(unsafe.Sizeof(node{})) + +type node struct { + // Multiple parts of the value are encoded as a single uint64 so that it + // can be atomically loaded and stored: + // value offset: uint32 (bits 0-31) + // value size : uint16 (bits 32-63) + value uint64 + + // A byte slice is 24 bytes. We are trying to save space here. + keyOffset uint32 // Immutable. No need to lock to access key. + keySize uint16 // Immutable. No need to lock to access key. + + // Height of the tower. + height uint16 + + // Most nodes do not need to use the full height of the tower, since the + // probability of each successive level decreases exponentially. Because + // these elements are never accessed, they do not need to be allocated. + // Therefore, when a node is allocated in the arena, its memory footprint + // is deliberately truncated to not include unneeded tower elements. + // + // All accesses to elements should use CAS operations, with no need to lock. + tower [maxHeight]uint32 +} + +type Skiplist struct { + height int32 // Current height. 1 <= height <= kMaxHeight. CAS. + head *node + ref int32 + arena *Arena + OnClose func() +} + +// IncrRef increases the refcount +func (s *Skiplist) IncrRef() { + atomic.AddInt32(&s.ref, 1) +} + +// DecrRef decrements the refcount, deallocating the Skiplist when done using it +func (s *Skiplist) DecrRef() { + newRef := atomic.AddInt32(&s.ref, -1) + if newRef > 0 { + return + } + if s.OnClose != nil { + s.OnClose() + } + + // Indicate we are closed. Good for testing. Also, lets GC reclaim memory. Race condition + // here would suggest we are accessing skiplist when we are supposed to have no reference! + s.arena = nil + // Since the head references the arena's buf, as long as the head is kept around + // GC can't release the buf. + s.head = nil +} + +func newNode(arena *Arena, key []byte, v y.ValueStruct, height int) *node { + // The base level is already allocated in the node struct. + offset := arena.putNode(height) + node := arena.getNode(offset) + node.keyOffset = arena.putKey(key) + node.keySize = uint16(len(key)) + node.height = uint16(height) + node.value = encodeValue(arena.putVal(v), v.EncodedSize()) + return node +} + +func encodeValue(valOffset uint32, valSize uint32) uint64 { + return uint64(valSize)<<32 | uint64(valOffset) +} + +func decodeValue(value uint64) (valOffset uint32, valSize uint32) { + valOffset = uint32(value) + valSize = uint32(value >> 32) + return +} + +// NewSkiplist makes a new empty skiplist, with a given arena size +func NewSkiplist(arenaSize int64) *Skiplist { + arena := newArena(arenaSize) + head := newNode(arena, nil, y.ValueStruct{}, maxHeight) + return &Skiplist{ + height: 1, + head: head, + arena: arena, + ref: 1, + } +} + +func (s *node) getValueOffset() (uint32, uint32) { + value := atomic.LoadUint64(&s.value) + return decodeValue(value) +} + +func (s *node) key(arena *Arena) []byte { + return arena.getKey(s.keyOffset, s.keySize) +} + +func (s *node) setValue(arena *Arena, v y.ValueStruct) { + valOffset := arena.putVal(v) + value := encodeValue(valOffset, v.EncodedSize()) + atomic.StoreUint64(&s.value, value) +} + +func (s *node) getNextOffset(h int) uint32 { + return atomic.LoadUint32(&s.tower[h]) +} + +func (s *node) casNextOffset(h int, old, val uint32) bool { + return atomic.CompareAndSwapUint32(&s.tower[h], old, val) +} + +// Returns true if key is strictly > n.key. +// If n is nil, this is an "end" marker and we return false. +//func (s *Skiplist) keyIsAfterNode(key []byte, n *node) bool { +// y.AssertTrue(n != s.head) +// return n != nil && y.CompareKeys(key, n.key) > 0 +//} + +func (s *Skiplist) randomHeight() int { + h := 1 + for h < maxHeight && z.FastRand() <= heightIncrease { + h++ + } + return h +} + +func (s *Skiplist) getNext(nd *node, height int) *node { + return s.arena.getNode(nd.getNextOffset(height)) +} + +// findNear finds the node near to key. +// If less=true, it finds rightmost node such that node.key < key (if allowEqual=false) or +// node.key <= key (if allowEqual=true). +// If less=false, it finds leftmost node such that node.key > key (if allowEqual=false) or +// node.key >= key (if allowEqual=true). +// Returns the node found. The bool returned is true if the node has key equal to given key. +func (s *Skiplist) findNear(key []byte, less bool, allowEqual bool) (*node, bool) { + x := s.head + level := int(s.getHeight() - 1) + for { + // Assume x.key < key. + next := s.getNext(x, level) + if next == nil { + // x.key < key < END OF LIST + if level > 0 { + // Can descend further to iterate closer to the end. + level-- + continue + } + // Level=0. Cannot descend further. Let's return something that makes sense. + if !less { + return nil, false + } + // Try to return x. Make sure it is not a head node. + if x == s.head { + return nil, false + } + return x, false + } + + nextKey := next.key(s.arena) + cmp := y.CompareKeys(key, nextKey) + if cmp > 0 { + // x.key < next.key < key. We can continue to move right. + x = next + continue + } + if cmp == 0 { + // x.key < key == next.key. + if allowEqual { + return next, true + } + if !less { + // We want >, so go to base level to grab the next bigger note. + return s.getNext(next, 0), false + } + // We want <. If not base level, we should go closer in the next level. + if level > 0 { + level-- + continue + } + // On base level. Return x. + if x == s.head { + return nil, false + } + return x, false + } + // cmp < 0. In other words, x.key < key < next. + if level > 0 { + level-- + continue + } + // At base level. Need to return something. + if !less { + return next, false + } + // Try to return x. Make sure it is not a head node. + if x == s.head { + return nil, false + } + return x, false + } +} + +// findSpliceForLevel returns (outBefore, outAfter) with outBefore.key <= key <= outAfter.key. +// The input "before" tells us where to start looking. +// If we found a node with the same key, then we return outBefore = outAfter. +// Otherwise, outBefore.key < key < outAfter.key. +func (s *Skiplist) findSpliceForLevel(key []byte, before *node, level int) (*node, *node) { + for { + // Assume before.key < key. + next := s.getNext(before, level) + if next == nil { + return before, next + } + nextKey := next.key(s.arena) + cmp := y.CompareKeys(key, nextKey) + if cmp == 0 { + // Equality case. + return next, next + } + if cmp < 0 { + // before.key < key < next.key. We are done for this level. + return before, next + } + before = next // Keep moving right on this level. + } +} + +func (s *Skiplist) getHeight() int32 { + return atomic.LoadInt32(&s.height) +} + +// Put inserts the key-value pair. +func (s *Skiplist) Put(key []byte, v y.ValueStruct) { + // Since we allow overwrite, we may not need to create a new node. We might not even need to + // increase the height. Let's defer these actions. + + listHeight := s.getHeight() + var prev [maxHeight + 1]*node + var next [maxHeight + 1]*node + prev[listHeight] = s.head + next[listHeight] = nil + for i := int(listHeight) - 1; i >= 0; i-- { + // Use higher level to speed up for current level. + prev[i], next[i] = s.findSpliceForLevel(key, prev[i+1], i) + if prev[i] == next[i] { + prev[i].setValue(s.arena, v) + return + } + } + + // We do need to create a new node. + height := s.randomHeight() + x := newNode(s.arena, key, v, height) + + // Try to increase s.height via CAS. + listHeight = s.getHeight() + for height > int(listHeight) { + if atomic.CompareAndSwapInt32(&s.height, listHeight, int32(height)) { + // Successfully increased skiplist.height. + break + } + listHeight = s.getHeight() + } + + // We always insert from the base level and up. After you add a node in base level, we cannot + // create a node in the level above because it would have discovered the node in the base level. + for i := 0; i < height; i++ { + for { + if prev[i] == nil { + y.AssertTrue(i > 1) // This cannot happen in base level. + // We haven't computed prev, next for this level because height exceeds old listHeight. + // For these levels, we expect the lists to be sparse, so we can just search from head. + prev[i], next[i] = s.findSpliceForLevel(key, s.head, i) + // Someone adds the exact same key before we are able to do so. This can only happen on + // the base level. But we know we are not on the base level. + y.AssertTrue(prev[i] != next[i]) + } + nextOffset := s.arena.getNodeOffset(next[i]) + x.tower[i] = nextOffset + if prev[i].casNextOffset(i, nextOffset, s.arena.getNodeOffset(x)) { + // Managed to insert x between prev[i] and next[i]. Go to the next level. + break + } + // CAS failed. We need to recompute prev and next. + // It is unlikely to be helpful to try to use a different level as we redo the search, + // because it is unlikely that lots of nodes are inserted between prev[i] and next[i]. + prev[i], next[i] = s.findSpliceForLevel(key, prev[i], i) + if prev[i] == next[i] { + y.AssertTruef(i == 0, "Equality can happen only on base level: %d", i) + prev[i].setValue(s.arena, v) + return + } + } + } +} + +// Empty returns if the Skiplist is empty. +func (s *Skiplist) Empty() bool { + return s.findLast() == nil +} + +// findLast returns the last element. If head (empty list), we return nil. All the find functions +// will NEVER return the head nodes. +func (s *Skiplist) findLast() *node { + n := s.head + level := int(s.getHeight()) - 1 + for { + next := s.getNext(n, level) + if next != nil { + n = next + continue + } + if level == 0 { + if n == s.head { + return nil + } + return n + } + level-- + } +} + +// Get gets the value associated with the key. It returns a valid value if it finds equal or earlier +// version of the same key. +func (s *Skiplist) Get(key []byte) y.ValueStruct { + n, _ := s.findNear(key, false, true) // findGreaterOrEqual. + if n == nil { + return y.ValueStruct{} + } + + nextKey := s.arena.getKey(n.keyOffset, n.keySize) + if !y.SameKey(key, nextKey) { + return y.ValueStruct{} + } + + valOffset, valSize := n.getValueOffset() + vs := s.arena.getVal(valOffset, valSize) + vs.Version = y.ParseTs(nextKey) + return vs +} + +// NewIterator returns a skiplist iterator. You have to Close() the iterator. +func (s *Skiplist) NewIterator() *Iterator { + s.IncrRef() + return &Iterator{list: s} +} + +// MemSize returns the size of the Skiplist in terms of how much memory is used within its internal +// arena. +func (s *Skiplist) MemSize() int64 { return s.arena.size() } + +// Iterator is an iterator over skiplist object. For new objects, you just +// need to initialize Iterator.list. +type Iterator struct { + list *Skiplist + n *node +} + +// Close frees the resources held by the iterator +func (s *Iterator) Close() error { + s.list.DecrRef() + return nil +} + +// Valid returns true iff the iterator is positioned at a valid node. +func (s *Iterator) Valid() bool { return s.n != nil } + +// Key returns the key at the current position. +func (s *Iterator) Key() []byte { + return s.list.arena.getKey(s.n.keyOffset, s.n.keySize) +} + +// Value returns value. +func (s *Iterator) Value() y.ValueStruct { + valOffset, valSize := s.n.getValueOffset() + return s.list.arena.getVal(valOffset, valSize) +} + +// ValueUint64 returns the uint64 value of the current node. +func (s *Iterator) ValueUint64() uint64 { + return s.n.value +} + +// Next advances to the next position. +func (s *Iterator) Next() { + y.AssertTrue(s.Valid()) + s.n = s.list.getNext(s.n, 0) +} + +// Prev advances to the previous position. +func (s *Iterator) Prev() { + y.AssertTrue(s.Valid()) + s.n, _ = s.list.findNear(s.Key(), true, false) // find <. No equality allowed. +} + +// Seek advances to the first entry with a key >= target. +func (s *Iterator) Seek(target []byte) { + s.n, _ = s.list.findNear(target, false, true) // find >=. +} + +// SeekForPrev finds an entry with key <= target. +func (s *Iterator) SeekForPrev(target []byte) { + s.n, _ = s.list.findNear(target, true, true) // find <=. +} + +// SeekToFirst seeks position at the first entry in list. +// Final state of iterator is Valid() iff list is not empty. +func (s *Iterator) SeekToFirst() { + s.n = s.list.getNext(s.list.head, 0) +} + +// SeekToLast seeks position at the last entry in list. +// Final state of iterator is Valid() iff list is not empty. +func (s *Iterator) SeekToLast() { + s.n = s.list.findLast() +} + +// UniIterator is a unidirectional memtable iterator. It is a thin wrapper around +// Iterator. We like to keep Iterator as before, because it is more powerful and +// we might support bidirectional iterators in the future. +type UniIterator struct { + iter *Iterator + reversed bool +} + +// NewUniIterator returns a UniIterator. +func (s *Skiplist) NewUniIterator(reversed bool) *UniIterator { + return &UniIterator{ + iter: s.NewIterator(), + reversed: reversed, + } +} + +// Next implements y.Interface +func (s *UniIterator) Next() { + if !s.reversed { + s.iter.Next() + } else { + s.iter.Prev() + } +} + +// Rewind implements y.Interface +func (s *UniIterator) Rewind() { + if !s.reversed { + s.iter.SeekToFirst() + } else { + s.iter.SeekToLast() + } +} + +// Seek implements y.Interface +func (s *UniIterator) Seek(key []byte) { + if !s.reversed { + s.iter.Seek(key) + } else { + s.iter.SeekForPrev(key) + } +} + +// Key implements y.Interface +func (s *UniIterator) Key() []byte { return s.iter.Key() } + +// Value implements y.Interface +func (s *UniIterator) Value() y.ValueStruct { return s.iter.Value() } + +// Valid implements y.Interface +func (s *UniIterator) Valid() bool { return s.iter.Valid() } + +// Close implements y.Interface (and frees up the iter's resources) +func (s *UniIterator) Close() error { return s.iter.Close() } diff --git a/vendor/github.com/dgraph-io/badger/v3/stream.go b/vendor/github.com/dgraph-io/badger/v3/stream.go new file mode 100644 index 0000000000..0614f19aa5 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/stream.go @@ -0,0 +1,491 @@ +/* + * Copyright 2018 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bytes" + "context" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + humanize "github.com/dustin/go-humanize" +) + +const batchSize = 16 << 20 // 16 MB + +// maxStreamSize is the maximum allowed size of a stream batch. This is a soft limit +// as a single list that is still over the limit will have to be sent as is since it +// cannot be split further. This limit prevents the framework from creating batches +// so big that sending them causes issues (e.g running into the max size gRPC limit). +var maxStreamSize = uint64(100 << 20) // 100MB + +// Stream provides a framework to concurrently iterate over a snapshot of Badger, pick up +// key-values, batch them up and call Send. Stream does concurrent iteration over many smaller key +// ranges. It does NOT send keys in lexicographical sorted order. To get keys in sorted +// order, use Iterator. +type Stream struct { + // Prefix to only iterate over certain range of keys. If set to nil (default), Stream would + // iterate over the entire DB. + Prefix []byte + + // Number of goroutines to use for iterating over key ranges. Defaults to 8. + NumGo int + + // Badger would produce log entries in Infof to indicate the progress of Stream. LogPrefix can + // be used to help differentiate them from other activities. Default is "Badger.Stream". + LogPrefix string + + // ChooseKey is invoked each time a new key is encountered. Note that this is not called + // on every version of the value, only the first encountered version (i.e. the highest version + // of the value a key has). ChooseKey can be left nil to select all keys. + // + // Note: Calls to ChooseKey are concurrent. + ChooseKey func(item *Item) bool + + // KeyToList, similar to ChooseKey, is only invoked on the highest version of the value. It + // is upto the caller to iterate over the versions and generate zero, one or more KVs. It + // is expected that the user would advance the iterator to go through the versions of the + // values. However, the user MUST immediately return from this function on the first encounter + // with a mismatching key. See example usage in ToList function. Can be left nil to use ToList + // function by default. + // + // KeyToList has access to z.Allocator accessible via stream.Allocator(itr.ThreadId). This + // allocator can be used to allocate KVs, to decrease the memory pressure on Go GC. Stream + // framework takes care of releasing those resources after calling Send. AllocRef does + // NOT need to be set in the returned KVList, as Stream framework would ignore that field, + // instead using the allocator assigned to that thread id. + // + // Note: Calls to KeyToList are concurrent. + KeyToList func(key []byte, itr *Iterator) (*pb.KVList, error) + + // This is the method where Stream sends the final output. All calls to Send are done by a + // single goroutine, i.e. logic within Send method can expect single threaded execution. + Send func(buf *z.Buffer) error + + // Read data above the sinceTs. All keys with version =< sinceTs will be ignored. + SinceTs uint64 + readTs uint64 + db *DB + rangeCh chan keyRange + kvChan chan *z.Buffer + nextStreamId uint32 + doneMarkers bool + scanned uint64 // used to estimate the ETA for data scan. + numProducers int32 +} + +// SendDoneMarkers when true would send out done markers on the stream. False by default. +func (st *Stream) SendDoneMarkers(done bool) { + st.doneMarkers = done +} + +// ToList is a default implementation of KeyToList. It picks up all valid versions of the key, +// skipping over deleted or expired keys. +func (st *Stream) ToList(key []byte, itr *Iterator) (*pb.KVList, error) { + a := itr.Alloc + ka := a.Copy(key) + + list := &pb.KVList{} + for ; itr.Valid(); itr.Next() { + item := itr.Item() + if item.IsDeletedOrExpired() { + break + } + if !bytes.Equal(key, item.Key()) { + // Break out on the first encounter with another key. + break + } + + kv := y.NewKV(a) + kv.Key = ka + + if err := item.Value(func(val []byte) error { + kv.Value = a.Copy(val) + return nil + + }); err != nil { + return nil, err + } + kv.Version = item.Version() + kv.ExpiresAt = item.ExpiresAt() + kv.UserMeta = a.Copy([]byte{item.UserMeta()}) + + list.Kv = append(list.Kv, kv) + if st.db.opt.NumVersionsToKeep == 1 { + break + } + + if item.DiscardEarlierVersions() { + break + } + } + return list, nil +} + +// keyRange is [start, end), including start, excluding end. Do ensure that the start, +// end byte slices are owned by keyRange struct. +func (st *Stream) produceRanges(ctx context.Context) { + ranges := st.db.Ranges(st.Prefix, 16) + y.AssertTrue(len(ranges) > 0) + y.AssertTrue(ranges[0].left == nil) + y.AssertTrue(ranges[len(ranges)-1].right == nil) + st.db.opt.Infof("Number of ranges found: %d\n", len(ranges)) + + // Sort in descending order of size. + sort.Slice(ranges, func(i, j int) bool { + return ranges[i].size > ranges[j].size + }) + for i, r := range ranges { + st.rangeCh <- *r + st.db.opt.Infof("Sent range %d for iteration: [%x, %x) of size: %s\n", + i, r.left, r.right, humanize.IBytes(uint64(r.size))) + } + close(st.rangeCh) +} + +// produceKVs picks up ranges from rangeCh, generates KV lists and sends them to kvChan. +func (st *Stream) produceKVs(ctx context.Context, threadId int) error { + atomic.AddInt32(&st.numProducers, 1) + defer atomic.AddInt32(&st.numProducers, -1) + + var txn *Txn + if st.readTs > 0 { + txn = st.db.NewTransactionAt(st.readTs, false) + } else { + txn = st.db.NewTransaction(false) + } + defer txn.Discard() + + // produceKVs is running iterate serially. So, we can define the outList here. + outList := z.NewBuffer(2*batchSize, "Stream.ProduceKVs") + defer func() { + // The outList variable changes. So, we need to evaluate the variable in the defer. DO NOT + // call `defer outList.Release()`. + outList.Release() + }() + + iterate := func(kr keyRange) error { + iterOpts := DefaultIteratorOptions + iterOpts.AllVersions = true + iterOpts.Prefix = st.Prefix + iterOpts.PrefetchValues = false + iterOpts.SinceTs = st.SinceTs + itr := txn.NewIterator(iterOpts) + itr.ThreadId = threadId + defer itr.Close() + + itr.Alloc = z.NewAllocator(1<<20, "Stream.Iterate") + defer itr.Alloc.Release() + + // This unique stream id is used to identify all the keys from this iteration. + streamId := atomic.AddUint32(&st.nextStreamId, 1) + var scanned int + + sendIt := func() error { + select { + case st.kvChan <- outList: + outList = z.NewBuffer(2*batchSize, "Stream.ProduceKVs") + atomic.AddUint64(&st.scanned, uint64(itr.scanned-scanned)) + scanned = itr.scanned + case <-ctx.Done(): + return ctx.Err() + } + return nil + } + + var prevKey []byte + for itr.Seek(kr.left); itr.Valid(); { + // it.Valid would only return true for keys with the provided Prefix in iterOpts. + item := itr.Item() + if bytes.Equal(item.Key(), prevKey) { + itr.Next() + continue + } + prevKey = append(prevKey[:0], item.Key()...) + + // Check if we reached the end of the key range. + if len(kr.right) > 0 && bytes.Compare(item.Key(), kr.right) >= 0 { + break + } + + // Check if we should pick this key. + if st.ChooseKey != nil && !st.ChooseKey(item) { + continue + } + + // Now convert to key value. + itr.Alloc.Reset() + list, err := st.KeyToList(item.KeyCopy(nil), itr) + if err != nil { + st.db.opt.Warningf("While reading key: %x, got error: %v", item.Key(), err) + continue + } + if list == nil || len(list.Kv) == 0 { + continue + } + for _, kv := range list.Kv { + kv.StreamId = streamId + KVToBuffer(kv, outList) + if outList.LenNoPadding() < batchSize { + continue + } + if err := sendIt(); err != nil { + return err + } + } + } + // Mark the stream as done. + if st.doneMarkers { + kv := &pb.KV{ + StreamId: streamId, + StreamDone: true, + } + KVToBuffer(kv, outList) + } + return sendIt() + } + + for { + select { + case kr, ok := <-st.rangeCh: + if !ok { + // Done with the keys. + return nil + } + if err := iterate(kr); err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (st *Stream) streamKVs(ctx context.Context) error { + onDiskSize, uncompressedSize := st.db.EstimateSize(st.Prefix) + // Manish has seen uncompressed size to be in 20% error margin. + uncompressedSize = uint64(float64(uncompressedSize) * 1.2) + st.db.opt.Infof("%s Streaming about %s of uncompressed data (%s on disk)\n", + st.LogPrefix, humanize.IBytes(uncompressedSize), humanize.IBytes(onDiskSize)) + + tickerDur := 5 * time.Second + var bytesSent uint64 + t := time.NewTicker(tickerDur) + defer t.Stop() + now := time.Now() + + sendBatch := func(batch *z.Buffer) error { + defer batch.Release() + sz := uint64(batch.LenNoPadding()) + if sz == 0 { + return nil + } + bytesSent += sz + // st.db.opt.Infof("%s Sending batch of size: %s.\n", st.LogPrefix, humanize.IBytes(sz)) + if err := st.Send(batch); err != nil { + st.db.opt.Warningf("Error while sending: %v\n", err) + return err + } + return nil + } + + slurp := func(batch *z.Buffer) error { + loop: + for { + // Send the batch immediately if it already exceeds the maximum allowed size. + // If the size of the batch exceeds maxStreamSize, break from the loop to + // avoid creating a batch that is so big that certain limits are reached. + if batch.LenNoPadding() > int(maxStreamSize) { + break loop + } + select { + case kvs, ok := <-st.kvChan: + if !ok { + break loop + } + y.AssertTrue(kvs != nil) + y.Check2(batch.Write(kvs.Bytes())) + y.Check(kvs.Release()) + + default: + break loop + } + } + return sendBatch(batch) + } // end of slurp. + + writeRate := y.NewRateMonitor(20) + scanRate := y.NewRateMonitor(20) +outer: + for { + var batch *z.Buffer + select { + case <-ctx.Done(): + return ctx.Err() + + case <-t.C: + // Instead of calculating speed over the entire lifetime, we average the speed over + // ticker duration. + writeRate.Capture(bytesSent) + scanned := atomic.LoadUint64(&st.scanned) + scanRate.Capture(scanned) + numProducers := atomic.LoadInt32(&st.numProducers) + + st.db.opt.Infof("%s [%s] Scan (%d): ~%s/%s at %s/sec. Sent: %s at %s/sec."+ + " jemalloc: %s\n", + st.LogPrefix, y.FixedDuration(time.Since(now)), numProducers, + y.IBytesToString(scanned, 1), humanize.IBytes(uncompressedSize), + humanize.IBytes(scanRate.Rate()), + y.IBytesToString(bytesSent, 1), humanize.IBytes(writeRate.Rate()), + humanize.IBytes(uint64(z.NumAllocBytes()))) + + case kvs, ok := <-st.kvChan: + if !ok { + break outer + } + y.AssertTrue(kvs != nil) + batch = kvs + + // Otherwise, slurp more keys into this batch. + if err := slurp(batch); err != nil { + return err + } + } + } + + st.db.opt.Infof("%s Sent data of size %s\n", st.LogPrefix, humanize.IBytes(bytesSent)) + return nil +} + +// Orchestrate runs Stream. It picks up ranges from the SSTables, then runs NumGo number of +// goroutines to iterate over these ranges and batch up KVs in lists. It concurrently runs a single +// goroutine to pick these lists, batch them up further and send to Output.Send. Orchestrate also +// spits logs out to Infof, using provided LogPrefix. Note that all calls to Output.Send +// are serial. In case any of these steps encounter an error, Orchestrate would stop execution and +// return that error. Orchestrate can be called multiple times, but in serial order. +func (st *Stream) Orchestrate(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + st.rangeCh = make(chan keyRange, 3) // Contains keys for posting lists. + + // kvChan should only have a small capacity to ensure that we don't buffer up too much data if + // sending is slow. Page size is set to 4MB, which is used to lazily cap the size of each + // KVList. To get 128MB buffer, we can set the channel size to 32. + st.kvChan = make(chan *z.Buffer, 32) + + if st.KeyToList == nil { + st.KeyToList = st.ToList + } + + // Picks up ranges from Badger, and sends them to rangeCh. + go st.produceRanges(ctx) + + errCh := make(chan error, st.NumGo) // Stores error by consumeKeys. + var wg sync.WaitGroup + for i := 0; i < st.NumGo; i++ { + wg.Add(1) + + go func(threadId int) { + defer wg.Done() + // Picks up ranges from rangeCh, generates KV lists, and sends them to kvChan. + if err := st.produceKVs(ctx, threadId); err != nil { + select { + case errCh <- err: + default: + } + } + }(i) + } + + // Pick up key-values from kvChan and send to stream. + kvErr := make(chan error, 1) + go func() { + // Picks up KV lists from kvChan, and sends them to Output. + err := st.streamKVs(ctx) + if err != nil { + cancel() // Stop all the go routines. + } + kvErr <- err + }() + wg.Wait() // Wait for produceKVs to be over. + close(st.kvChan) // Now we can close kvChan. + defer func() { + // If due to some error, we have buffers left in kvChan, we should release them. + for buf := range st.kvChan { + buf.Release() + } + }() + + select { + case err := <-errCh: // Check error from produceKVs. + return err + default: + } + + // Wait for key streaming to be over. + err := <-kvErr + return err +} + +func (db *DB) newStream() *Stream { + return &Stream{ + db: db, + NumGo: db.opt.NumGoroutines, + LogPrefix: "Badger.Stream", + } +} + +// NewStream creates a new Stream. +func (db *DB) NewStream() *Stream { + if db.opt.managedTxns { + panic("This API can not be called in managed mode.") + } + return db.newStream() +} + +// NewStreamAt creates a new Stream at a particular timestamp. Should only be used with managed DB. +func (db *DB) NewStreamAt(readTs uint64) *Stream { + if !db.opt.managedTxns { + panic("This API can only be called in managed mode.") + } + stream := db.newStream() + stream.readTs = readTs + return stream +} + +func BufferToKVList(buf *z.Buffer) (*pb.KVList, error) { + var list pb.KVList + err := buf.SliceIterate(func(s []byte) error { + kv := new(pb.KV) + if err := kv.Unmarshal(s); err != nil { + return err + } + list.Kv = append(list.Kv, kv) + return nil + }) + return &list, err +} + +func KVToBuffer(kv *pb.KV, buf *z.Buffer) { + out := buf.SliceAllocate(kv.Size()) + y.Check2(kv.MarshalToSizedBuffer(out)) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/stream_writer.go b/vendor/github.com/dgraph-io/badger/v3/stream_writer.go new file mode 100644 index 0000000000..4613718cce --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/stream_writer.go @@ -0,0 +1,459 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "encoding/hex" + "fmt" + "sync" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/table" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + humanize "github.com/dustin/go-humanize" + "github.com/pkg/errors" +) + +// StreamWriter is used to write data coming from multiple streams. The streams must not have any +// overlapping key ranges. Within each stream, the keys must be sorted. Badger Stream framework is +// capable of generating such an output. So, this StreamWriter can be used at the other end to build +// BadgerDB at a much faster pace by writing SSTables (and value logs) directly to LSM tree levels +// without causing any compactions at all. This is way faster than using batched writer or using +// transactions, but only applicable in situations where the keys are pre-sorted and the DB is being +// bootstrapped. Existing data would get deleted when using this writer. So, this is only useful +// when restoring from backup or replicating DB across servers. +// +// StreamWriter should not be called on in-use DB instances. It is designed only to bootstrap new +// DBs. +type StreamWriter struct { + writeLock sync.Mutex + db *DB + done func() + throttle *y.Throttle + maxVersion uint64 + writers map[uint32]*sortedWriter +} + +// NewStreamWriter creates a StreamWriter. Right after creating StreamWriter, Prepare must be +// called. The memory usage of a StreamWriter is directly proportional to the number of streams +// possible. So, efforts must be made to keep the number of streams low. Stream framework would +// typically use 16 goroutines and hence create 16 streams. +func (db *DB) NewStreamWriter() *StreamWriter { + return &StreamWriter{ + db: db, + // throttle shouldn't make much difference. Memory consumption is based on the number of + // concurrent streams being processed. + throttle: y.NewThrottle(16), + writers: make(map[uint32]*sortedWriter), + } +} + +// Prepare should be called before writing any entry to StreamWriter. It deletes all data present in +// existing DB, stops compactions and any writes being done by other means. Be very careful when +// calling Prepare, because it could result in permanent data loss. Not calling Prepare would result +// in a corrupt Badger instance. +func (sw *StreamWriter) Prepare() error { + sw.writeLock.Lock() + defer sw.writeLock.Unlock() + + done, err := sw.db.dropAll() + + // Ensure that done() is never called more than once. + var once sync.Once + sw.done = func() { once.Do(done) } + + return err +} + +// Write writes KVList to DB. Each KV within the list contains the stream id which StreamWriter +// would use to demux the writes. Write is thread safe and can be called concurrently by multiple +// goroutines. +func (sw *StreamWriter) Write(buf *z.Buffer) error { + if buf.LenNoPadding() == 0 { + return nil + } + + // closedStreams keeps track of all streams which are going to be marked as done. We are + // keeping track of all streams so that we can close them at the end, after inserting all + // the valid kvs. + closedStreams := make(map[uint32]struct{}) + streamReqs := make(map[uint32]*request) + + err := buf.SliceIterate(func(s []byte) error { + var kv pb.KV + if err := kv.Unmarshal(s); err != nil { + return err + } + if kv.StreamDone { + closedStreams[kv.StreamId] = struct{}{} + return nil + } + + // Panic if some kv comes after stream has been marked as closed. + if _, ok := closedStreams[kv.StreamId]; ok { + panic(fmt.Sprintf("write performed on closed stream: %d", kv.StreamId)) + } + + var meta, userMeta byte + if len(kv.Meta) > 0 { + meta = kv.Meta[0] + } + if len(kv.UserMeta) > 0 { + userMeta = kv.UserMeta[0] + } + if sw.maxVersion < kv.Version { + sw.maxVersion = kv.Version + } + e := &Entry{ + Key: y.KeyWithTs(kv.Key, kv.Version), + Value: y.Copy(kv.Value), + UserMeta: userMeta, + ExpiresAt: kv.ExpiresAt, + meta: meta, + } + // If the value can be collocated with the key in LSM tree, we can skip + // writing the value to value log. + req := streamReqs[kv.StreamId] + if req == nil { + req = &request{} + streamReqs[kv.StreamId] = req + } + req.Entries = append(req.Entries, e) + return nil + }) + if err != nil { + return err + } + + all := make([]*request, 0, len(streamReqs)) + for _, req := range streamReqs { + all = append(all, req) + } + + sw.writeLock.Lock() + defer sw.writeLock.Unlock() + + // We are writing all requests to vlog even if some request belongs to already closed stream. + // It is safe to do because we are panicking while writing to sorted writer, which will be nil + // for closed stream. At restart, stream writer will drop all the data in Prepare function. + if err := sw.db.vlog.write(all); err != nil { + return err + } + + for streamID, req := range streamReqs { + writer, ok := sw.writers[streamID] + if !ok { + var err error + writer, err = sw.newWriter(streamID) + if err != nil { + return y.Wrapf(err, "failed to create writer with ID %d", streamID) + } + sw.writers[streamID] = writer + } + + if writer == nil { + panic(fmt.Sprintf("write performed on closed stream: %d", streamID)) + } + + writer.reqCh <- req + } + + // Now we can close any streams if required. We will make writer for + // the closed streams as nil. + for streamId := range closedStreams { + writer, ok := sw.writers[streamId] + if !ok { + sw.db.opt.Logger.Warningf("Trying to close stream: %d, but no sorted "+ + "writer found for it", streamId) + continue + } + + writer.closer.SignalAndWait() + if err := writer.Done(); err != nil { + return err + } + + sw.writers[streamId] = nil + } + return nil +} + +// Flush is called once we are done writing all the entries. It syncs DB directories. It also +// updates Oracle with maxVersion found in all entries (if DB is not managed). +func (sw *StreamWriter) Flush() error { + sw.writeLock.Lock() + defer sw.writeLock.Unlock() + + defer sw.done() + + for _, writer := range sw.writers { + if writer != nil { + writer.closer.SignalAndWait() + } + } + + for _, writer := range sw.writers { + if writer == nil { + continue + } + if err := writer.Done(); err != nil { + return err + } + } + + if !sw.db.opt.managedTxns { + if sw.db.orc != nil { + sw.db.orc.Stop() + } + sw.db.orc = newOracle(sw.db.opt) + sw.db.orc.nextTxnTs = sw.maxVersion + sw.db.orc.txnMark.Done(sw.maxVersion) + sw.db.orc.readMark.Done(sw.maxVersion) + sw.db.orc.incrementNextTs() + } + + // Wait for all files to be written. + if err := sw.throttle.Finish(); err != nil { + return err + } + + // Sort tables at the end. + for _, l := range sw.db.lc.levels { + l.sortTables() + } + + // Now sync the directories, so all the files are registered. + if sw.db.opt.ValueDir != sw.db.opt.Dir { + if err := sw.db.syncDir(sw.db.opt.ValueDir); err != nil { + return err + } + } + if err := sw.db.syncDir(sw.db.opt.Dir); err != nil { + return err + } + return sw.db.lc.validate() +} + +// Cancel signals all goroutines to exit. Calling defer sw.Cancel() immediately after creating a new StreamWriter +// ensures that writes are unblocked even upon early return. Note that dropAll() is not called here, so any +// partially written data will not be erased until a new StreamWriter is initialized. +func (sw *StreamWriter) Cancel() { + sw.writeLock.Lock() + defer sw.writeLock.Unlock() + + for _, writer := range sw.writers { + if writer != nil { + writer.closer.Signal() + } + } + for _, writer := range sw.writers { + if writer != nil { + writer.closer.Wait() + } + } + + if err := sw.throttle.Finish(); err != nil { + sw.db.opt.Errorf("error in throttle.Finish: %+v", err) + } + + // Handle Cancel() being called before Prepare(). + if sw.done != nil { + sw.done() + } +} + +type sortedWriter struct { + db *DB + throttle *y.Throttle + opts table.Options + + builder *table.Builder + lastKey []byte + streamID uint32 + reqCh chan *request + // Have separate closer for each writer, as it can be closed at any time. + closer *z.Closer +} + +func (sw *StreamWriter) newWriter(streamID uint32) (*sortedWriter, error) { + bopts := buildTableOptions(sw.db) + for i := 2; i < sw.db.opt.MaxLevels; i++ { + bopts.TableSize *= uint64(sw.db.opt.TableSizeMultiplier) + } + w := &sortedWriter{ + db: sw.db, + opts: bopts, + streamID: streamID, + throttle: sw.throttle, + builder: table.NewTableBuilder(bopts), + reqCh: make(chan *request, 3), + closer: z.NewCloser(1), + } + + go w.handleRequests() + return w, nil +} + +func (w *sortedWriter) handleRequests() { + defer w.closer.Done() + + process := func(req *request) { + for i, e := range req.Entries { + // If badger is running in InMemory mode, len(req.Ptrs) == 0. + var vs y.ValueStruct + if e.skipVlogAndSetThreshold(w.db.valueThreshold()) { + vs = y.ValueStruct{ + Value: e.Value, + Meta: e.meta, + UserMeta: e.UserMeta, + ExpiresAt: e.ExpiresAt, + } + } else { + vptr := req.Ptrs[i] + vs = y.ValueStruct{ + Value: vptr.Encode(), + Meta: e.meta | bitValuePointer, + UserMeta: e.UserMeta, + ExpiresAt: e.ExpiresAt, + } + } + if err := w.Add(e.Key, vs); err != nil { + panic(err) + } + } + } + + for { + select { + case req := <-w.reqCh: + process(req) + case <-w.closer.HasBeenClosed(): + close(w.reqCh) + for req := range w.reqCh { + process(req) + } + return + } + } +} + +// Add adds key and vs to sortedWriter. +func (w *sortedWriter) Add(key []byte, vs y.ValueStruct) error { + if len(w.lastKey) > 0 && y.CompareKeys(key, w.lastKey) <= 0 { + return errors.Errorf("keys not in sorted order (last key: %s, key: %s)", + hex.Dump(w.lastKey), hex.Dump(key)) + } + + sameKey := y.SameKey(key, w.lastKey) + + // Same keys should go into the same SSTable. + if !sameKey && w.builder.ReachedCapacity() { + if err := w.send(false); err != nil { + return err + } + } + + w.lastKey = y.SafeCopy(w.lastKey, key) + var vp valuePointer + if vs.Meta&bitValuePointer > 0 { + vp.Decode(vs.Value) + } + + w.builder.Add(key, vs, vp.Len) + return nil +} + +func (w *sortedWriter) send(done bool) error { + if err := w.throttle.Do(); err != nil { + return err + } + go func(builder *table.Builder) { + err := w.createTable(builder) + w.throttle.Done(err) + }(w.builder) + // If done is true, this indicates we can close the writer. + // No need to allocate underlying TableBuilder now. + if done { + w.builder = nil + return nil + } + + w.builder = table.NewTableBuilder(w.opts) + return nil +} + +// Done is called once we are done writing all keys and valueStructs +// to sortedWriter. It completes writing current SST to disk. +func (w *sortedWriter) Done() error { + if w.builder.Empty() { + w.builder.Close() + // Assign builder as nil, so that underlying memory can be garbage collected. + w.builder = nil + return nil + } + + return w.send(true) +} + +func (w *sortedWriter) createTable(builder *table.Builder) error { + defer builder.Close() + if builder.Empty() { + builder.Finish() + return nil + } + + fileID := w.db.lc.reserveFileID() + var tbl *table.Table + if w.db.opt.InMemory { + data := builder.Finish() + var err error + if tbl, err = table.OpenInMemoryTable(data, fileID, builder.Opts()); err != nil { + return err + } + } else { + var err error + fname := table.NewFilename(fileID, w.db.opt.Dir) + if tbl, err = table.CreateTable(fname, builder); err != nil { + return err + } + } + lc := w.db.lc + + lhandler := lc.levels[len(lc.levels)-1] + // Now that table can be opened successfully, let's add this to the MANIFEST. + change := &pb.ManifestChange{ + Id: tbl.ID(), + KeyId: tbl.KeyID(), + Op: pb.ManifestChange_CREATE, + Level: uint32(lhandler.level), + Compression: uint32(tbl.CompressionType()), + } + if err := w.db.manifest.addChanges([]*pb.ManifestChange{change}); err != nil { + return err + } + + // We are not calling lhandler.replaceTables() here, as it sorts tables on every addition. + // We can sort all tables only once during Flush() call. + lhandler.addTable(tbl) + + // Release the ref held by OpenTable. + _ = tbl.DecrRef() + w.db.opt.Infof("Table created: %d at level: %d for stream: %d. Size: %s\n", + fileID, lhandler.level, w.streamID, humanize.IBytes(uint64(tbl.Size()))) + return nil +} diff --git a/vendor/github.com/dgraph-io/badger/v3/structs.go b/vendor/github.com/dgraph-io/badger/v3/structs.go new file mode 100644 index 0000000000..c17f818cf7 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/structs.go @@ -0,0 +1,225 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "encoding/binary" + "fmt" + "time" + "unsafe" +) + +type valuePointer struct { + Fid uint32 + Len uint32 + Offset uint32 +} + +const vptrSize = unsafe.Sizeof(valuePointer{}) + +func (p valuePointer) Less(o valuePointer) bool { + if p.Fid != o.Fid { + return p.Fid < o.Fid + } + if p.Offset != o.Offset { + return p.Offset < o.Offset + } + return p.Len < o.Len +} + +func (p valuePointer) IsZero() bool { + return p.Fid == 0 && p.Offset == 0 && p.Len == 0 +} + +// Encode encodes Pointer into byte buffer. +func (p valuePointer) Encode() []byte { + b := make([]byte, vptrSize) + // Copy over the content from p to b. + *(*valuePointer)(unsafe.Pointer(&b[0])) = p + return b +} + +// Decode decodes the value pointer into the provided byte buffer. +func (p *valuePointer) Decode(b []byte) { + // Copy over data from b into p. Using *p=unsafe.pointer(...) leads to + // pointer alignment issues. See https://github.com/dgraph-io/badger/issues/1096 + // and comment https://github.com/dgraph-io/badger/pull/1097#pullrequestreview-307361714 + copy(((*[vptrSize]byte)(unsafe.Pointer(p))[:]), b[:vptrSize]) +} + +// header is used in value log as a header before Entry. +type header struct { + klen uint32 + vlen uint32 + expiresAt uint64 + meta byte + userMeta byte +} + +const ( + // Maximum possible size of the header. The maximum size of header struct will be 18 but the + // maximum size of varint encoded header will be 21. + maxHeaderSize = 21 +) + +// Encode encodes the header into []byte. The provided []byte should be atleast 5 bytes. The +// function will panic if out []byte isn't large enough to hold all the values. +// The encoded header looks like +// +------+----------+------------+--------------+-----------+ +// | Meta | UserMeta | Key Length | Value Length | ExpiresAt | +// +------+----------+------------+--------------+-----------+ +func (h header) Encode(out []byte) int { + out[0], out[1] = h.meta, h.userMeta + index := 2 + index += binary.PutUvarint(out[index:], uint64(h.klen)) + index += binary.PutUvarint(out[index:], uint64(h.vlen)) + index += binary.PutUvarint(out[index:], h.expiresAt) + return index +} + +// Decode decodes the given header from the provided byte slice. +// Returns the number of bytes read. +func (h *header) Decode(buf []byte) int { + h.meta, h.userMeta = buf[0], buf[1] + index := 2 + klen, count := binary.Uvarint(buf[index:]) + h.klen = uint32(klen) + index += count + vlen, count := binary.Uvarint(buf[index:]) + h.vlen = uint32(vlen) + index += count + h.expiresAt, count = binary.Uvarint(buf[index:]) + return index + count +} + +// DecodeFrom reads the header from the hashReader. +// Returns the number of bytes read. +func (h *header) DecodeFrom(reader *hashReader) (int, error) { + var err error + h.meta, err = reader.ReadByte() + if err != nil { + return 0, err + } + h.userMeta, err = reader.ReadByte() + if err != nil { + return 0, err + } + klen, err := binary.ReadUvarint(reader) + if err != nil { + return 0, err + } + h.klen = uint32(klen) + vlen, err := binary.ReadUvarint(reader) + if err != nil { + return 0, err + } + h.vlen = uint32(vlen) + h.expiresAt, err = binary.ReadUvarint(reader) + if err != nil { + return 0, err + } + return reader.bytesRead, nil +} + +// Entry provides Key, Value, UserMeta and ExpiresAt. This struct can be used by +// the user to set data. +type Entry struct { + Key []byte + Value []byte + ExpiresAt uint64 // time.Unix + version uint64 + offset uint32 // offset is an internal field. + UserMeta byte + meta byte + + // Fields maintained internally. + hlen int // Length of the header. + valThreshold int64 +} + +func (e *Entry) isZero() bool { + return len(e.Key) == 0 +} + +func (e *Entry) estimateSizeAndSetThreshold(threshold int64) int64 { + if e.valThreshold == 0 { + e.valThreshold = threshold + } + k := int64(len(e.Key)) + v := int64(len(e.Value)) + if v < e.valThreshold { + return k + v + 2 // Meta, UserMeta + } + return k + 12 + 2 // 12 for ValuePointer, 2 for metas. +} + +func (e *Entry) skipVlogAndSetThreshold(threshold int64) bool { + if e.valThreshold == 0 { + e.valThreshold = threshold + } + return int64(len(e.Value)) < e.valThreshold +} + +func (e Entry) print(prefix string) { + fmt.Printf("%s Key: %s Meta: %d UserMeta: %d Offset: %d len(val)=%d", + prefix, e.Key, e.meta, e.UserMeta, e.offset, len(e.Value)) +} + +// NewEntry creates a new entry with key and value passed in args. This newly created entry can be +// set in a transaction by calling txn.SetEntry(). All other properties of Entry can be set by +// calling WithMeta, WithDiscard, WithTTL methods on it. +// This function uses key and value reference, hence users must +// not modify key and value until the end of transaction. +func NewEntry(key, value []byte) *Entry { + return &Entry{ + Key: key, + Value: value, + } +} + +// WithMeta adds meta data to Entry e. This byte is stored alongside the key +// and can be used as an aid to interpret the value or store other contextual +// bits corresponding to the key-value pair of entry. +func (e *Entry) WithMeta(meta byte) *Entry { + e.UserMeta = meta + return e +} + +// WithDiscard adds a marker to Entry e. This means all the previous versions of the key (of the +// Entry) will be eligible for garbage collection. +// This method is only useful if you have set a higher limit for options.NumVersionsToKeep. The +// default setting is 1, in which case, this function doesn't add any more benefit. If however, you +// have a higher setting for NumVersionsToKeep (in Dgraph, we set it to infinity), you can use this +// method to indicate that all the older versions can be discarded and removed during compactions. +func (e *Entry) WithDiscard() *Entry { + e.meta = bitDiscardEarlierVersions + return e +} + +// WithTTL adds time to live duration to Entry e. Entry stored with a TTL would automatically expire +// after the time has elapsed, and will be eligible for garbage collection. +func (e *Entry) WithTTL(dur time.Duration) *Entry { + e.ExpiresAt = uint64(time.Now().Add(dur).Unix()) + return e +} + +// withMergeBit sets merge bit in entry's metadata. This +// function is called by MergeOperator's Add method. +func (e *Entry) withMergeBit() *Entry { + e.meta = bitMergeEntry + return e +} diff --git a/vendor/github.com/dgraph-io/badger/v3/table/README.md b/vendor/github.com/dgraph-io/badger/v3/table/README.md new file mode 100644 index 0000000000..19276079ef --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/table/README.md @@ -0,0 +1,108 @@ +Size of table is 123,217,667 bytes for all benchmarks. + +# BenchmarkRead +``` +$ go test -bench ^BenchmarkRead$ -run ^$ -count 3 +goos: linux +goarch: amd64 +pkg: github.com/dgraph-io/badger/table +BenchmarkRead-16 10 154074944 ns/op +BenchmarkRead-16 10 154340411 ns/op +BenchmarkRead-16 10 151914489 ns/op +PASS +ok github.com/dgraph-io/badger/table 22.467s +``` + +Size of table is 123,217,667 bytes, which is ~118MB. + +The rate is ~762MB/s using LoadToRAM (when table is in RAM). + +To read a 64MB table, this would take ~0.084s, which is negligible. + +# BenchmarkReadAndBuild +```go +$ go test -bench BenchmarkReadAndBuild -run ^$ -count 3 +goos: linux +goarch: amd64 +pkg: github.com/dgraph-io/badger/table +BenchmarkReadAndBuild-16 1 1026755231 ns/op +BenchmarkReadAndBuild-16 1 1009543316 ns/op +BenchmarkReadAndBuild-16 1 1039920546 ns/op +PASS +ok github.com/dgraph-io/badger/table 12.081s +``` + +The rate is ~123MB/s. To build a 64MB table, this would take ~0.56s. Note that this +does NOT include the flushing of the table to disk. All we are doing above is +reading one table (which is in RAM) and write one table in memory. + +The table building takes 0.56-0.084s ~ 0.4823s. + +# BenchmarkReadMerged +Below, we merge 5 tables. The total size remains unchanged at ~122M. + +```go +$ go test -bench ReadMerged -run ^$ -count 3 +goos: linux +goarch: amd64 +pkg: github.com/dgraph-io/badger/table +BenchmarkReadMerged-16 2 977588975 ns/op +BenchmarkReadMerged-16 2 982140738 ns/op +BenchmarkReadMerged-16 2 962046017 ns/op +PASS +ok github.com/dgraph-io/badger/table 27.433s +``` + +The rate is ~120MB/s. To read a 64MB table using merge iterator, this would take ~0.53s. + +# BenchmarkRandomRead + +```go +go test -bench BenchmarkRandomRead$ -run ^$ -count 3 +goos: linux +goarch: amd64 +pkg: github.com/dgraph-io/badger/table +BenchmarkRandomRead-16 500000 2645 ns/op +BenchmarkRandomRead-16 500000 2648 ns/op +BenchmarkRandomRead-16 500000 2614 ns/op +PASS +ok github.com/dgraph-io/badger/table 50.850s +``` +For random read benchmarking, we are randomly reading a key and verifying its value. + +# DB Open benchmark +1. Create badger DB with 2 billion key-value pairs (about 380GB of data) +``` +badger fill -m 2000 --dir="/tmp/data" --sorted +``` +2. Clear buffers and swap memory +``` +free -mh && sync && echo 3 | sudo tee /proc/sys/vm/drop_caches && sudo swapoff -a && sudo swapon -a && free -mh +``` +Also flush disk buffers +``` +blockdev --flushbufs /dev/nvme0n1p4 +``` +3. Run the benchmark +``` +go test -run=^$ github.com/dgraph-io/badger -bench ^BenchmarkDBOpen$ -benchdir="/tmp/data" -v + +badger 2019/06/04 17:15:56 INFO: 126 tables out of 1028 opened in 3.017s +badger 2019/06/04 17:15:59 INFO: 257 tables out of 1028 opened in 6.014s +badger 2019/06/04 17:16:02 INFO: 387 tables out of 1028 opened in 9.017s +badger 2019/06/04 17:16:05 INFO: 516 tables out of 1028 opened in 12.025s +badger 2019/06/04 17:16:08 INFO: 645 tables out of 1028 opened in 15.013s +badger 2019/06/04 17:16:11 INFO: 775 tables out of 1028 opened in 18.008s +badger 2019/06/04 17:16:14 INFO: 906 tables out of 1028 opened in 21.003s +badger 2019/06/04 17:16:17 INFO: All 1028 tables opened in 23.851s +badger 2019/06/04 17:16:17 INFO: Replaying file id: 1998 at offset: 332000 +badger 2019/06/04 17:16:17 INFO: Replay took: 9.81µs +goos: linux +goarch: amd64 +pkg: github.com/dgraph-io/badger +BenchmarkDBOpen-16 1 23930082140 ns/op +PASS +ok github.com/dgraph-io/badger 24.076s + +``` +It takes about 23.851s to open a DB with 2 billion sorted key-value entries. diff --git a/vendor/github.com/dgraph-io/badger/v3/table/builder.go b/vendor/github.com/dgraph-io/badger/v3/table/builder.go new file mode 100644 index 0000000000..5bab48a72f --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/table/builder.go @@ -0,0 +1,591 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 table + +import ( + "crypto/aes" + "math" + "runtime" + "sync" + "sync/atomic" + "unsafe" + + "github.com/dgraph-io/badger/v3/fb" + "github.com/golang/protobuf/proto" + "github.com/golang/snappy" + fbs "github.com/google/flatbuffers/go" + "github.com/pkg/errors" + + "github.com/dgraph-io/badger/v3/options" + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" +) + +const ( + KB = 1024 + MB = KB * 1024 + + // When a block is encrypted, it's length increases. We add 256 bytes of padding to + // handle cases when block size increases. This is an approximate number. + padding = 256 +) + +type header struct { + overlap uint16 // Overlap with base key. + diff uint16 // Length of the diff. +} + +const headerSize = uint16(unsafe.Sizeof(header{})) + +// Encode encodes the header. +func (h header) Encode() []byte { + var b [4]byte + *(*header)(unsafe.Pointer(&b[0])) = h + return b[:] +} + +// Decode decodes the header. +func (h *header) Decode(buf []byte) { + // Copy over data from buf into h. Using *h=unsafe.pointer(...) leads to + // pointer alignment issues. See https://github.com/dgraph-io/badger/issues/1096 + // and comment https://github.com/dgraph-io/badger/pull/1097#pullrequestreview-307361714 + copy(((*[headerSize]byte)(unsafe.Pointer(h))[:]), buf[:headerSize]) +} + +// bblock represents a block that is being compressed/encrypted in the background. +type bblock struct { + data []byte + baseKey []byte // Base key for the current block. + entryOffsets []uint32 // Offsets of entries present in current block. + end int // Points to the end offset of the block. +} + +// Builder is used in building a table. +type Builder struct { + // Typically tens or hundreds of meg. This is for one single file. + alloc *z.Allocator + curBlock *bblock + compressedSize uint32 + uncompressedSize uint32 + + lenOffsets uint32 + estimatedSize uint32 + keyHashes []uint32 // Used for building the bloomfilter. + opts *Options + maxVersion uint64 + onDiskSize uint32 + staleDataSize int + + // Used to concurrently compress/encrypt blocks. + wg sync.WaitGroup + blockChan chan *bblock + blockList []*bblock +} + +func (b *Builder) allocate(need int) []byte { + bb := b.curBlock + if len(bb.data[bb.end:]) < need { + // We need to reallocate. 1GB is the max size that the allocator can allocate. + // While reallocating, if doubling exceeds that limit, then put the upper bound on it. + sz := 2 * len(bb.data) + if sz > (1 << 30) { + sz = 1 << 30 + } + if bb.end+need > sz { + sz = bb.end + need + } + tmp := b.alloc.Allocate(sz) + copy(tmp, bb.data) + bb.data = tmp + } + bb.end += need + return bb.data[bb.end-need : bb.end] +} + +// append appends to curBlock.data +func (b *Builder) append(data []byte) { + dst := b.allocate(len(data)) + y.AssertTrue(len(data) == copy(dst, data)) +} + +const maxAllocatorInitialSz = 256 << 20 + +// NewTableBuilder makes a new TableBuilder. +func NewTableBuilder(opts Options) *Builder { + sz := 2 * int(opts.TableSize) + if sz > maxAllocatorInitialSz { + sz = maxAllocatorInitialSz + } + b := &Builder{ + alloc: opts.AllocPool.Get(sz, "TableBuilder"), + opts: &opts, + } + b.alloc.Tag = "Builder" + b.curBlock = &bblock{ + data: b.alloc.Allocate(opts.BlockSize + padding), + } + b.opts.tableCapacity = uint64(float64(b.opts.TableSize) * 0.95) + + // If encryption or compression is not enabled, do not start compression/encryption goroutines + // and write directly to the buffer. + if b.opts.Compression == options.None && b.opts.DataKey == nil { + return b + } + + count := 2 * runtime.NumCPU() + b.blockChan = make(chan *bblock, count*2) + + b.wg.Add(count) + for i := 0; i < count; i++ { + go b.handleBlock() + } + return b +} + +func (b *Builder) handleBlock() { + defer b.wg.Done() + + doCompress := b.opts.Compression != options.None + for item := range b.blockChan { + // Extract the block. + blockBuf := item.data[:item.end] + // Compress the block. + if doCompress { + out, err := b.compressData(blockBuf) + y.Check(err) + blockBuf = out + } + if b.shouldEncrypt() { + out, err := b.encrypt(blockBuf) + y.Check(y.Wrapf(err, "Error while encrypting block in table builder.")) + blockBuf = out + } + + // BlockBuf should always less than or equal to allocated space. If the blockBuf is greater + // than allocated space that means the data from this block cannot be stored in its + // existing location. + allocatedSpace := (item.end) + padding + 1 + y.AssertTrue(len(blockBuf) <= allocatedSpace) + + // blockBuf was allocated on allocator. So, we don't need to copy it over. + item.data = blockBuf + item.end = len(blockBuf) + atomic.AddUint32(&b.compressedSize, uint32(len(blockBuf))) + } +} + +// Close closes the TableBuilder. +func (b *Builder) Close() { + b.opts.AllocPool.Return(b.alloc) +} + +// Empty returns whether it's empty. +func (b *Builder) Empty() bool { return len(b.keyHashes) == 0 } + +// keyDiff returns a suffix of newKey that is different from b.baseKey. +func (b *Builder) keyDiff(newKey []byte) []byte { + var i int + for i = 0; i < len(newKey) && i < len(b.curBlock.baseKey); i++ { + if newKey[i] != b.curBlock.baseKey[i] { + break + } + } + return newKey[i:] +} + +func (b *Builder) addHelper(key []byte, v y.ValueStruct, vpLen uint32) { + b.keyHashes = append(b.keyHashes, y.Hash(y.ParseKey(key))) + + if version := y.ParseTs(key); version > b.maxVersion { + b.maxVersion = version + } + + // diffKey stores the difference of key with baseKey. + var diffKey []byte + if len(b.curBlock.baseKey) == 0 { + // Make a copy. Builder should not keep references. Otherwise, caller has to be very careful + // and will have to make copies of keys every time they add to builder, which is even worse. + b.curBlock.baseKey = append(b.curBlock.baseKey[:0], key...) + diffKey = key + } else { + diffKey = b.keyDiff(key) + } + + y.AssertTrue(len(key)-len(diffKey) <= math.MaxUint16) + y.AssertTrue(len(diffKey) <= math.MaxUint16) + + h := header{ + overlap: uint16(len(key) - len(diffKey)), + diff: uint16(len(diffKey)), + } + + // store current entry's offset + b.curBlock.entryOffsets = append(b.curBlock.entryOffsets, uint32(b.curBlock.end)) + + // Layout: header, diffKey, value. + b.append(h.Encode()) + b.append(diffKey) + + dst := b.allocate(int(v.EncodedSize())) + v.Encode(dst) + + // Add the vpLen to the onDisk size. We'll add the size of the block to + // onDisk size in Finish() function. + b.onDiskSize += vpLen +} + +/* +Structure of Block. ++-------------------+---------------------+--------------------+--------------+------------------+ +| Entry1 | Entry2 | Entry3 | Entry4 | Entry5 | ++-------------------+---------------------+--------------------+--------------+------------------+ +| Entry6 | ... | ... | ... | EntryN | ++-------------------+---------------------+--------------------+--------------+------------------+ +| Block Meta(contains list of offsets used| Block Meta Size | Block | Checksum Size | +| to perform binary search in the block) | (4 Bytes) | Checksum | (4 Bytes) | ++-----------------------------------------+--------------------+--------------+------------------+ +*/ +// In case the data is encrypted, the "IV" is added to the end of the block. +func (b *Builder) finishBlock() { + if len(b.curBlock.entryOffsets) == 0 { + return + } + // Append the entryOffsets and its length. + b.append(y.U32SliceToBytes(b.curBlock.entryOffsets)) + b.append(y.U32ToBytes(uint32(len(b.curBlock.entryOffsets)))) + + checksum := b.calculateChecksum(b.curBlock.data[:b.curBlock.end]) + + // Append the block checksum and its length. + b.append(checksum) + b.append(y.U32ToBytes(uint32(len(checksum)))) + + b.blockList = append(b.blockList, b.curBlock) + atomic.AddUint32(&b.uncompressedSize, uint32(b.curBlock.end)) + + // Add length of baseKey (rounded to next multiple of 4 because of alignment). + // Add another 40 Bytes, these additional 40 bytes consists of + // 12 bytes of metadata of flatbuffer + // 8 bytes for Key in flat buffer + // 8 bytes for offset + // 8 bytes for the len + // 4 bytes for the size of slice while SliceAllocate + b.lenOffsets += uint32(int(math.Ceil(float64(len(b.curBlock.baseKey))/4))*4) + 40 + + // If compression/encryption is enabled, we need to send the block to the blockChan. + if b.blockChan != nil { + b.blockChan <- b.curBlock + } + return +} + +func (b *Builder) shouldFinishBlock(key []byte, value y.ValueStruct) bool { + // If there is no entry till now, we will return false. + if len(b.curBlock.entryOffsets) <= 0 { + return false + } + + // Integer overflow check for statements below. + y.AssertTrue((uint32(len(b.curBlock.entryOffsets))+1)*4+4+8+4 < math.MaxUint32) + // We should include current entry also in size, that's why +1 to len(b.entryOffsets). + entriesOffsetsSize := uint32((len(b.curBlock.entryOffsets)+1)*4 + + 4 + // size of list + 8 + // Sum64 in checksum proto + 4) // checksum length + estimatedSize := uint32(b.curBlock.end) + uint32(6 /*header size for entry*/) + + uint32(len(key)) + uint32(value.EncodedSize()) + entriesOffsetsSize + + if b.shouldEncrypt() { + // IV is added at the end of the block, while encrypting. + // So, size of IV is added to estimatedSize. + estimatedSize += aes.BlockSize + } + + // Integer overflow check for table size. + y.AssertTrue(uint64(b.curBlock.end)+uint64(estimatedSize) < math.MaxUint32) + + return estimatedSize > uint32(b.opts.BlockSize) +} + +// AddStaleKey is same is Add function but it also increments the internal +// staleDataSize counter. This value will be used to prioritize this table for +// compaction. +func (b *Builder) AddStaleKey(key []byte, v y.ValueStruct, valueLen uint32) { + // Rough estimate based on how much space it will occupy in the SST. + b.staleDataSize += len(key) + len(v.Value) + 4 /* entry offset */ + 4 /* header size */ + b.addInternal(key, v, valueLen, true) +} + +// Add adds a key-value pair to the block. +func (b *Builder) Add(key []byte, value y.ValueStruct, valueLen uint32) { + b.addInternal(key, value, valueLen, false) +} + +func (b *Builder) addInternal(key []byte, value y.ValueStruct, valueLen uint32, isStale bool) { + if b.shouldFinishBlock(key, value) { + if isStale { + // This key will be added to tableIndex and it is stale. + b.staleDataSize += len(key) + 4 /* len */ + 4 /* offset */ + } + b.finishBlock() + // Create a new block and start writing. + b.curBlock = &bblock{ + data: b.alloc.Allocate(b.opts.BlockSize + padding), + } + } + b.addHelper(key, value, valueLen) +} + +// TODO: vvv this was the comment on ReachedCapacity. +// FinalSize returns the *rough* final size of the array, counting the header which is +// not yet written. +// TODO: Look into why there is a discrepancy. I suspect it is because of Write(empty, empty) +// at the end. The diff can vary. + +// ReachedCapacity returns true if we... roughly (?) reached capacity? +func (b *Builder) ReachedCapacity() bool { + // If encryption/compression is enabled then use the compresssed size. + sumBlockSizes := atomic.LoadUint32(&b.compressedSize) + if b.opts.Compression == options.None && b.opts.DataKey == nil { + sumBlockSizes = b.uncompressedSize + } + blocksSize := sumBlockSizes + // actual length of current buffer + uint32(len(b.curBlock.entryOffsets)*4) + // all entry offsets size + 4 + // count of all entry offsets + 8 + // checksum bytes + 4 // checksum length + + estimateSz := blocksSize + + 4 + // Index length + b.lenOffsets + + return uint64(estimateSz) > b.opts.tableCapacity +} + +// Finish finishes the table by appending the index. +/* +The table structure looks like ++---------+------------+-----------+---------------+ +| Block 1 | Block 2 | Block 3 | Block 4 | ++---------+------------+-----------+---------------+ +| Block 5 | Block 6 | Block ... | Block N | ++---------+------------+-----------+---------------+ +| Index | Index Size | Checksum | Checksum Size | ++---------+------------+-----------+---------------+ +*/ +// In case the data is encrypted, the "IV" is added to the end of the index. +func (b *Builder) Finish() []byte { + bd := b.Done() + buf := make([]byte, bd.Size) + written := bd.Copy(buf) + y.AssertTrue(written == len(buf)) + return buf +} + +type buildData struct { + blockList []*bblock + index []byte + checksum []byte + Size int + alloc *z.Allocator +} + +func (bd *buildData) Copy(dst []byte) int { + var written int + for _, bl := range bd.blockList { + written += copy(dst[written:], bl.data[:bl.end]) + } + written += copy(dst[written:], bd.index) + written += copy(dst[written:], y.U32ToBytes(uint32(len(bd.index)))) + + written += copy(dst[written:], bd.checksum) + written += copy(dst[written:], y.U32ToBytes(uint32(len(bd.checksum)))) + return written +} + +func (b *Builder) Done() buildData { + b.finishBlock() // This will never start a new block. + if b.blockChan != nil { + close(b.blockChan) + } + // Wait for block handler to finish. + b.wg.Wait() + + if len(b.blockList) == 0 { + return buildData{} + } + bd := buildData{ + blockList: b.blockList, + alloc: b.alloc, + } + + var f y.Filter + if b.opts.BloomFalsePositive > 0 { + bits := y.BloomBitsPerKey(len(b.keyHashes), b.opts.BloomFalsePositive) + f = y.NewFilter(b.keyHashes, bits) + } + index, dataSize := b.buildIndex(f) + + var err error + if b.shouldEncrypt() { + index, err = b.encrypt(index) + y.Check(err) + } + checksum := b.calculateChecksum(index) + + bd.index = index + bd.checksum = checksum + bd.Size = int(dataSize) + len(index) + len(checksum) + 4 + 4 + return bd +} + +func (b *Builder) calculateChecksum(data []byte) []byte { + // Build checksum for the index. + checksum := pb.Checksum{ + // TODO: The checksum type should be configurable from the + // options. + // We chose to use CRC32 as the default option because + // it performed better compared to xxHash64. + // See the BenchmarkChecksum in table_test.go file + // Size => 1024 B 2048 B + // CRC32 => 63.7 ns/op 112 ns/op + // xxHash64 => 87.5 ns/op 158 ns/op + Sum: y.CalculateChecksum(data, pb.Checksum_CRC32C), + Algo: pb.Checksum_CRC32C, + } + + // Write checksum to the file. + chksum, err := proto.Marshal(&checksum) + y.Check(err) + // Write checksum size. + return chksum +} + +// DataKey returns datakey of the builder. +func (b *Builder) DataKey() *pb.DataKey { + return b.opts.DataKey +} + +func (b *Builder) Opts() *Options { + return b.opts +} + +// encrypt will encrypt the given data and appends IV to the end of the encrypted data. +// This should be only called only after checking shouldEncrypt method. +func (b *Builder) encrypt(data []byte) ([]byte, error) { + iv, err := y.GenerateIV() + if err != nil { + return data, y.Wrapf(err, "Error while generating IV in Builder.encrypt") + } + needSz := len(data) + len(iv) + dst := b.alloc.Allocate(needSz) + + if err = y.XORBlock(dst[:len(data)], data, b.DataKey().Data, iv); err != nil { + return data, y.Wrapf(err, "Error while encrypting in Builder.encrypt") + } + + y.AssertTrue(len(iv) == copy(dst[len(data):], iv)) + return dst, nil +} + +// shouldEncrypt tells us whether to encrypt the data or not. +// We encrypt only if the data key exist. Otherwise, not. +func (b *Builder) shouldEncrypt() bool { + return b.opts.DataKey != nil +} + +// compressData compresses the given data. +func (b *Builder) compressData(data []byte) ([]byte, error) { + switch b.opts.Compression { + case options.None: + return data, nil + case options.Snappy: + sz := snappy.MaxEncodedLen(len(data)) + dst := b.alloc.Allocate(sz) + return snappy.Encode(dst, data), nil + case options.ZSTD: + sz := y.ZSTDCompressBound(len(data)) + dst := b.alloc.Allocate(sz) + return y.ZSTDCompress(dst, data, b.opts.ZSTDCompressionLevel) + } + return nil, errors.New("Unsupported compression type") +} + +func (b *Builder) buildIndex(bloom []byte) ([]byte, uint32) { + builder := fbs.NewBuilder(3 << 20) + + boList, dataSize := b.writeBlockOffsets(builder) + // Write block offset vector the the idxBuilder. + fb.TableIndexStartOffsetsVector(builder, len(boList)) + + // Write individual block offsets in reverse order to work around how Flatbuffers expects it. + for i := len(boList) - 1; i >= 0; i-- { + builder.PrependUOffsetT(boList[i]) + } + boEnd := builder.EndVector(len(boList)) + + var bfoff fbs.UOffsetT + // Write the bloom filter. + if len(bloom) > 0 { + bfoff = builder.CreateByteVector(bloom) + } + b.onDiskSize += dataSize + fb.TableIndexStart(builder) + fb.TableIndexAddOffsets(builder, boEnd) + fb.TableIndexAddBloomFilter(builder, bfoff) + fb.TableIndexAddMaxVersion(builder, b.maxVersion) + fb.TableIndexAddUncompressedSize(builder, b.uncompressedSize) + fb.TableIndexAddKeyCount(builder, uint32(len(b.keyHashes))) + fb.TableIndexAddOnDiskSize(builder, b.onDiskSize) + fb.TableIndexAddStaleDataSize(builder, uint32(b.staleDataSize)) + builder.Finish(fb.TableIndexEnd(builder)) + + buf := builder.FinishedBytes() + index := fb.GetRootAsTableIndex(buf, 0) + // Mutate the ondisk size to include the size of the index as well. + y.AssertTrue(index.MutateOnDiskSize(index.OnDiskSize() + uint32(len(buf)))) + return buf, dataSize +} + +// writeBlockOffsets writes all the blockOffets in b.offsets and returns the +// offsets for the newly written items. +func (b *Builder) writeBlockOffsets(builder *fbs.Builder) ([]fbs.UOffsetT, uint32) { + var startOffset uint32 + var uoffs []fbs.UOffsetT + for _, bl := range b.blockList { + uoff := b.writeBlockOffset(builder, bl, startOffset) + uoffs = append(uoffs, uoff) + startOffset += uint32(bl.end) + } + return uoffs, startOffset +} + +// writeBlockOffset writes the given key,offset,len triple to the indexBuilder. +// It returns the offset of the newly written blockoffset. +func (b *Builder) writeBlockOffset( + builder *fbs.Builder, bl *bblock, startOffset uint32) fbs.UOffsetT { + // Write the key to the buffer. + k := builder.CreateByteVector(bl.baseKey) + + // Build the blockOffset. + fb.BlockOffsetStart(builder) + fb.BlockOffsetAddKey(builder, k) + fb.BlockOffsetAddOffset(builder, startOffset) + fb.BlockOffsetAddLen(builder, uint32(bl.end)) + return fb.BlockOffsetEnd(builder) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/table/iterator.go b/vendor/github.com/dgraph-io/badger/v3/table/iterator.go new file mode 100644 index 0000000000..26c9043c28 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/table/iterator.go @@ -0,0 +1,568 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 table + +import ( + "bytes" + "fmt" + "io" + "sort" + + "github.com/dgraph-io/badger/v3/fb" + "github.com/dgraph-io/badger/v3/y" +) + +type blockIterator struct { + data []byte + idx int // Idx of the entry inside a block + err error + baseKey []byte + key []byte + val []byte + entryOffsets []uint32 + block *block + + tableID uint64 + blockID int + // prevOverlap stores the overlap of the previous key with the base key. + // This avoids unnecessary copy of base key when the overlap is same for multiple keys. + prevOverlap uint16 +} + +func (itr *blockIterator) setBlock(b *block) { + // Decrement the ref for the old block. If the old block was compressed, we + // might be able to reuse it. + itr.block.decrRef() + + itr.block = b + itr.err = nil + itr.idx = 0 + itr.baseKey = itr.baseKey[:0] + itr.prevOverlap = 0 + itr.key = itr.key[:0] + itr.val = itr.val[:0] + // Drop the index from the block. We don't need it anymore. + itr.data = b.data[:b.entriesIndexStart] + itr.entryOffsets = b.entryOffsets +} + +// setIdx sets the iterator to the entry at index i and set it's key and value. +func (itr *blockIterator) setIdx(i int) { + itr.idx = i + if i >= len(itr.entryOffsets) || i < 0 { + itr.err = io.EOF + return + } + itr.err = nil + startOffset := int(itr.entryOffsets[i]) + + // Set base key. + if len(itr.baseKey) == 0 { + var baseHeader header + baseHeader.Decode(itr.data) + itr.baseKey = itr.data[headerSize : headerSize+baseHeader.diff] + } + + var endOffset int + // idx points to the last entry in the block. + if itr.idx+1 == len(itr.entryOffsets) { + endOffset = len(itr.data) + } else { + // idx point to some entry other than the last one in the block. + // EndOffset of the current entry is the start offset of the next entry. + endOffset = int(itr.entryOffsets[itr.idx+1]) + } + defer func() { + if r := recover(); r != nil { + var debugBuf bytes.Buffer + fmt.Fprintf(&debugBuf, "==== Recovered====\n") + fmt.Fprintf(&debugBuf, "Table ID: %d\nBlock ID: %d\nEntry Idx: %d\nData len: %d\n"+ + "StartOffset: %d\nEndOffset: %d\nEntryOffsets len: %d\nEntryOffsets: %v\n", + itr.tableID, itr.blockID, itr.idx, len(itr.data), startOffset, endOffset, + len(itr.entryOffsets), itr.entryOffsets) + panic(debugBuf.String()) + } + }() + + entryData := itr.data[startOffset:endOffset] + var h header + h.Decode(entryData) + // Header contains the length of key overlap and difference compared to the base key. If the key + // before this one had the same or better key overlap, we can avoid copying that part into + // itr.key. But, if the overlap was lesser, we could copy over just that portion. + if h.overlap > itr.prevOverlap { + itr.key = append(itr.key[:itr.prevOverlap], itr.baseKey[itr.prevOverlap:h.overlap]...) + } + itr.prevOverlap = h.overlap + valueOff := headerSize + h.diff + diffKey := entryData[headerSize:valueOff] + itr.key = append(itr.key[:h.overlap], diffKey...) + itr.val = entryData[valueOff:] +} + +func (itr *blockIterator) Valid() bool { + return itr != nil && itr.err == nil +} + +func (itr *blockIterator) Error() error { + return itr.err +} + +func (itr *blockIterator) Close() { + itr.block.decrRef() +} + +var ( + origin = 0 + current = 1 +) + +// seek brings us to the first block element that is >= input key. +func (itr *blockIterator) seek(key []byte, whence int) { + itr.err = nil + startIndex := 0 // This tells from which index we should start binary search. + + switch whence { + case origin: + // We don't need to do anything. startIndex is already at 0 + case current: + startIndex = itr.idx + } + + foundEntryIdx := sort.Search(len(itr.entryOffsets), func(idx int) bool { + // If idx is less than start index then just return false. + if idx < startIndex { + return false + } + itr.setIdx(idx) + return y.CompareKeys(itr.key, key) >= 0 + }) + itr.setIdx(foundEntryIdx) +} + +// seekToFirst brings us to the first element. +func (itr *blockIterator) seekToFirst() { + itr.setIdx(0) +} + +// seekToLast brings us to the last element. +func (itr *blockIterator) seekToLast() { + itr.setIdx(len(itr.entryOffsets) - 1) +} + +func (itr *blockIterator) next() { + itr.setIdx(itr.idx + 1) +} + +func (itr *blockIterator) prev() { + itr.setIdx(itr.idx - 1) +} + +// Iterator is an iterator for a Table. +type Iterator struct { + t *Table + bpos int + bi blockIterator + err error + + // Internally, Iterator is bidirectional. However, we only expose the + // unidirectional functionality for now. + opt int // Valid options are REVERSED and NOCACHE. +} + +// NewIterator returns a new iterator of the Table +func (t *Table) NewIterator(opt int) *Iterator { + t.IncrRef() // Important. + ti := &Iterator{t: t, opt: opt} + return ti +} + +// Close closes the iterator (and it must be called). +func (itr *Iterator) Close() error { + itr.bi.Close() + return itr.t.DecrRef() +} + +func (itr *Iterator) reset() { + itr.bpos = 0 + itr.err = nil +} + +// Valid follows the y.Iterator interface +func (itr *Iterator) Valid() bool { + return itr.err == nil +} + +func (itr *Iterator) useCache() bool { + return itr.opt&NOCACHE == 0 +} + +func (itr *Iterator) seekToFirst() { + numBlocks := itr.t.offsetsLength() + if numBlocks == 0 { + itr.err = io.EOF + return + } + itr.bpos = 0 + block, err := itr.t.block(itr.bpos, itr.useCache()) + if err != nil { + itr.err = err + return + } + itr.bi.tableID = itr.t.id + itr.bi.blockID = itr.bpos + itr.bi.setBlock(block) + itr.bi.seekToFirst() + itr.err = itr.bi.Error() +} + +func (itr *Iterator) seekToLast() { + numBlocks := itr.t.offsetsLength() + if numBlocks == 0 { + itr.err = io.EOF + return + } + itr.bpos = numBlocks - 1 + block, err := itr.t.block(itr.bpos, itr.useCache()) + if err != nil { + itr.err = err + return + } + itr.bi.tableID = itr.t.id + itr.bi.blockID = itr.bpos + itr.bi.setBlock(block) + itr.bi.seekToLast() + itr.err = itr.bi.Error() +} + +func (itr *Iterator) seekHelper(blockIdx int, key []byte) { + itr.bpos = blockIdx + block, err := itr.t.block(blockIdx, itr.useCache()) + if err != nil { + itr.err = err + return + } + itr.bi.tableID = itr.t.id + itr.bi.blockID = itr.bpos + itr.bi.setBlock(block) + itr.bi.seek(key, origin) + itr.err = itr.bi.Error() +} + +// seekFrom brings us to a key that is >= input key. +func (itr *Iterator) seekFrom(key []byte, whence int) { + itr.err = nil + switch whence { + case origin: + itr.reset() + case current: + } + + var ko fb.BlockOffset + idx := sort.Search(itr.t.offsetsLength(), func(idx int) bool { + // Offsets should never return false since we're iterating within the OffsetsLength. + y.AssertTrue(itr.t.offsets(&ko, idx)) + return y.CompareKeys(ko.KeyBytes(), key) > 0 + }) + if idx == 0 { + // The smallest key in our table is already strictly > key. We can return that. + // This is like a SeekToFirst. + itr.seekHelper(0, key) + return + } + + // block[idx].smallest is > key. + // Since idx>0, we know block[idx-1].smallest is <= key. + // There are two cases. + // 1) Everything in block[idx-1] is strictly < key. In this case, we should go to the first + // element of block[idx]. + // 2) Some element in block[idx-1] is >= key. We should go to that element. + itr.seekHelper(idx-1, key) + if itr.err == io.EOF { + // Case 1. Need to visit block[idx]. + if idx == itr.t.offsetsLength() { + // If idx == len(itr.t.blockIndex), then input key is greater than ANY element of table. + // There's nothing we can do. Valid() should return false as we seek to end of table. + return + } + // Since block[idx].smallest is > key. This is essentially a block[idx].SeekToFirst. + itr.seekHelper(idx, key) + } + // Case 2: No need to do anything. We already did the seek in block[idx-1]. +} + +// seek will reset iterator and seek to >= key. +func (itr *Iterator) seek(key []byte) { + itr.seekFrom(key, origin) +} + +// seekForPrev will reset iterator and seek to <= key. +func (itr *Iterator) seekForPrev(key []byte) { + // TODO: Optimize this. We shouldn't have to take a Prev step. + itr.seekFrom(key, origin) + if !bytes.Equal(itr.Key(), key) { + itr.prev() + } +} + +func (itr *Iterator) next() { + itr.err = nil + + if itr.bpos >= itr.t.offsetsLength() { + itr.err = io.EOF + return + } + + if len(itr.bi.data) == 0 { + block, err := itr.t.block(itr.bpos, itr.useCache()) + if err != nil { + itr.err = err + return + } + itr.bi.tableID = itr.t.id + itr.bi.blockID = itr.bpos + itr.bi.setBlock(block) + itr.bi.seekToFirst() + itr.err = itr.bi.Error() + return + } + + itr.bi.next() + if !itr.bi.Valid() { + itr.bpos++ + itr.bi.data = nil + itr.next() + return + } +} + +func (itr *Iterator) prev() { + itr.err = nil + if itr.bpos < 0 { + itr.err = io.EOF + return + } + + if len(itr.bi.data) == 0 { + block, err := itr.t.block(itr.bpos, itr.useCache()) + if err != nil { + itr.err = err + return + } + itr.bi.tableID = itr.t.id + itr.bi.blockID = itr.bpos + itr.bi.setBlock(block) + itr.bi.seekToLast() + itr.err = itr.bi.Error() + return + } + + itr.bi.prev() + if !itr.bi.Valid() { + itr.bpos-- + itr.bi.data = nil + itr.prev() + return + } +} + +// Key follows the y.Iterator interface. +// Returns the key with timestamp. +func (itr *Iterator) Key() []byte { + return itr.bi.key +} + +// Value follows the y.Iterator interface +func (itr *Iterator) Value() (ret y.ValueStruct) { + ret.Decode(itr.bi.val) + return +} + +// ValueCopy copies the current value and returns it as decoded +// ValueStruct. +func (itr *Iterator) ValueCopy() (ret y.ValueStruct) { + dst := y.Copy(itr.bi.val) + ret.Decode(dst) + return +} + +// Next follows the y.Iterator interface +func (itr *Iterator) Next() { + if itr.opt&REVERSED == 0 { + itr.next() + } else { + itr.prev() + } +} + +// Rewind follows the y.Iterator interface +func (itr *Iterator) Rewind() { + if itr.opt&REVERSED == 0 { + itr.seekToFirst() + } else { + itr.seekToLast() + } +} + +// Seek follows the y.Iterator interface +func (itr *Iterator) Seek(key []byte) { + if itr.opt&REVERSED == 0 { + itr.seek(key) + } else { + itr.seekForPrev(key) + } +} + +var ( + REVERSED int = 2 + NOCACHE int = 4 +) + +// ConcatIterator concatenates the sequences defined by several iterators. (It only works with +// TableIterators, probably just because it's faster to not be so generic.) +type ConcatIterator struct { + idx int // Which iterator is active now. + cur *Iterator + iters []*Iterator // Corresponds to tables. + tables []*Table // Disregarding reversed, this is in ascending order. + options int // Valid options are REVERSED and NOCACHE. +} + +// NewConcatIterator creates a new concatenated iterator +func NewConcatIterator(tbls []*Table, opt int) *ConcatIterator { + iters := make([]*Iterator, len(tbls)) + for i := 0; i < len(tbls); i++ { + // Increment the reference count. Since, we're not creating the iterator right now. + // Here, We'll hold the reference of the tables, till the lifecycle of the iterator. + tbls[i].IncrRef() + + // Save cycles by not initializing the iterators until needed. + // iters[i] = tbls[i].NewIterator(reversed) + } + return &ConcatIterator{ + options: opt, + iters: iters, + tables: tbls, + idx: -1, // Not really necessary because s.it.Valid()=false, but good to have. + } +} + +func (s *ConcatIterator) setIdx(idx int) { + s.idx = idx + if idx < 0 || idx >= len(s.iters) { + s.cur = nil + return + } + if s.iters[idx] == nil { + s.iters[idx] = s.tables[idx].NewIterator(s.options) + } + s.cur = s.iters[s.idx] +} + +// Rewind implements y.Interface +func (s *ConcatIterator) Rewind() { + if len(s.iters) == 0 { + return + } + if s.options&REVERSED == 0 { + s.setIdx(0) + } else { + s.setIdx(len(s.iters) - 1) + } + s.cur.Rewind() +} + +// Valid implements y.Interface +func (s *ConcatIterator) Valid() bool { + return s.cur != nil && s.cur.Valid() +} + +// Key implements y.Interface +func (s *ConcatIterator) Key() []byte { + return s.cur.Key() +} + +// Value implements y.Interface +func (s *ConcatIterator) Value() y.ValueStruct { + return s.cur.Value() +} + +// Seek brings us to element >= key if reversed is false. Otherwise, <= key. +func (s *ConcatIterator) Seek(key []byte) { + var idx int + if s.options&REVERSED == 0 { + idx = sort.Search(len(s.tables), func(i int) bool { + return y.CompareKeys(s.tables[i].Biggest(), key) >= 0 + }) + } else { + n := len(s.tables) + idx = n - 1 - sort.Search(n, func(i int) bool { + return y.CompareKeys(s.tables[n-1-i].Smallest(), key) <= 0 + }) + } + if idx >= len(s.tables) || idx < 0 { + s.setIdx(-1) + return + } + // For reversed=false, we know s.tables[i-1].Biggest() < key. Thus, the + // previous table cannot possibly contain key. + s.setIdx(idx) + s.cur.Seek(key) +} + +// Next advances our concat iterator. +func (s *ConcatIterator) Next() { + s.cur.Next() + if s.cur.Valid() { + // Nothing to do. Just stay with the current table. + return + } + for { // In case there are empty tables. + if s.options&REVERSED == 0 { + s.setIdx(s.idx + 1) + } else { + s.setIdx(s.idx - 1) + } + if s.cur == nil { + // End of list. Valid will become false. + return + } + s.cur.Rewind() + if s.cur.Valid() { + break + } + } +} + +// Close implements y.Interface. +func (s *ConcatIterator) Close() error { + for _, t := range s.tables { + // DeReference the tables while closing the iterator. + if err := t.DecrRef(); err != nil { + return err + } + } + for _, it := range s.iters { + if it == nil { + continue + } + if err := it.Close(); err != nil { + return y.Wrap(err, "ConcatIterator") + } + } + return nil +} diff --git a/vendor/github.com/dgraph-io/badger/v3/table/merge_iterator.go b/vendor/github.com/dgraph-io/badger/v3/table/merge_iterator.go new file mode 100644 index 0000000000..789a24fd7b --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/table/merge_iterator.go @@ -0,0 +1,231 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 table + +import ( + "bytes" + + "github.com/dgraph-io/badger/v3/y" +) + +// MergeIterator merges multiple iterators. +// NOTE: MergeIterator owns the array of iterators and is responsible for closing them. +type MergeIterator struct { + left node + right node + small *node + + curKey []byte + reverse bool +} + +type node struct { + valid bool + key []byte + iter y.Iterator + + // The two iterators are type asserted from `y.Iterator`, used to inline more function calls. + // Calling functions on concrete types is much faster (about 25-30%) than calling the + // interface's function. + merge *MergeIterator + concat *ConcatIterator +} + +func (n *node) setIterator(iter y.Iterator) { + n.iter = iter + // It's okay if the type assertion below fails and n.merge/n.concat are set to nil. + // We handle the nil values of merge and concat in all the methods. + n.merge, _ = iter.(*MergeIterator) + n.concat, _ = iter.(*ConcatIterator) +} + +func (n *node) setKey() { + switch { + case n.merge != nil: + n.valid = n.merge.small.valid + if n.valid { + n.key = n.merge.small.key + } + case n.concat != nil: + n.valid = n.concat.Valid() + if n.valid { + n.key = n.concat.Key() + } + default: + n.valid = n.iter.Valid() + if n.valid { + n.key = n.iter.Key() + } + } +} + +func (n *node) next() { + switch { + case n.merge != nil: + n.merge.Next() + case n.concat != nil: + n.concat.Next() + default: + n.iter.Next() + } + n.setKey() +} + +func (n *node) rewind() { + n.iter.Rewind() + n.setKey() +} + +func (n *node) seek(key []byte) { + n.iter.Seek(key) + n.setKey() +} + +func (mi *MergeIterator) fix() { + if !mi.bigger().valid { + return + } + if !mi.small.valid { + mi.swapSmall() + return + } + cmp := y.CompareKeys(mi.small.key, mi.bigger().key) + switch { + case cmp == 0: // Both the keys are equal. + // In case of same keys, move the right iterator ahead. + mi.right.next() + if &mi.right == mi.small { + mi.swapSmall() + } + return + case cmp < 0: // Small is less than bigger(). + if mi.reverse { + mi.swapSmall() + } else { + // we don't need to do anything. Small already points to the smallest. + } + return + default: // bigger() is less than small. + if mi.reverse { + // Do nothing since we're iterating in reverse. Small currently points to + // the bigger key and that's okay in reverse iteration. + } else { + mi.swapSmall() + } + return + } +} + +func (mi *MergeIterator) bigger() *node { + if mi.small == &mi.left { + return &mi.right + } + return &mi.left +} + +func (mi *MergeIterator) swapSmall() { + if mi.small == &mi.left { + mi.small = &mi.right + return + } + if mi.small == &mi.right { + mi.small = &mi.left + return + } +} + +// Next returns the next element. If it is the same as the current key, ignore it. +func (mi *MergeIterator) Next() { + for mi.Valid() { + if !bytes.Equal(mi.small.key, mi.curKey) { + break + } + mi.small.next() + mi.fix() + } + mi.setCurrent() +} + +func (mi *MergeIterator) setCurrent() { + mi.curKey = append(mi.curKey[:0], mi.small.key...) +} + +// Rewind seeks to first element (or last element for reverse iterator). +func (mi *MergeIterator) Rewind() { + mi.left.rewind() + mi.right.rewind() + mi.fix() + mi.setCurrent() +} + +// Seek brings us to element with key >= given key. +func (mi *MergeIterator) Seek(key []byte) { + mi.left.seek(key) + mi.right.seek(key) + mi.fix() + mi.setCurrent() +} + +// Valid returns whether the MergeIterator is at a valid element. +func (mi *MergeIterator) Valid() bool { + return mi.small.valid +} + +// Key returns the key associated with the current iterator. +func (mi *MergeIterator) Key() []byte { + return mi.small.key +} + +// Value returns the value associated with the iterator. +func (mi *MergeIterator) Value() y.ValueStruct { + return mi.small.iter.Value() +} + +// Close implements y.Iterator. +func (mi *MergeIterator) Close() error { + err1 := mi.left.iter.Close() + err2 := mi.right.iter.Close() + if err1 != nil { + return y.Wrap(err1, "MergeIterator") + } + return y.Wrap(err2, "MergeIterator") +} + +// NewMergeIterator creates a merge iterator. +func NewMergeIterator(iters []y.Iterator, reverse bool) y.Iterator { + switch len(iters) { + case 0: + return nil + case 1: + return iters[0] + case 2: + mi := &MergeIterator{ + reverse: reverse, + } + mi.left.setIterator(iters[0]) + mi.right.setIterator(iters[1]) + // Assign left iterator randomly. This will be fixed when user calls rewind/seek. + mi.small = &mi.left + return mi + } + mid := len(iters) / 2 + return NewMergeIterator( + []y.Iterator{ + NewMergeIterator(iters[:mid], reverse), + NewMergeIterator(iters[mid:], reverse), + }, reverse) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/table/table.go b/vendor/github.com/dgraph-io/badger/v3/table/table.go new file mode 100644 index 0000000000..1378f9a36b --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/table/table.go @@ -0,0 +1,842 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 table + +import ( + "bytes" + "crypto/aes" + "encoding/binary" + "fmt" + "math" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/golang/protobuf/proto" + "github.com/golang/snappy" + "github.com/pkg/errors" + + "github.com/dgraph-io/badger/v3/fb" + "github.com/dgraph-io/badger/v3/options" + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto" + "github.com/dgraph-io/ristretto/z" +) + +const fileSuffix = ".sst" +const intSize = int(unsafe.Sizeof(int(0))) + +// Options contains configurable options for Table/Builder. +type Options struct { + // Options for Opening/Building Table. + + // Open tables in read only mode. + ReadOnly bool + MetricsEnabled bool + + // Maximum size of the table. + TableSize uint64 + tableCapacity uint64 // 0.9x TableSize. + + // ChkMode is the checksum verification mode for Table. + ChkMode options.ChecksumVerificationMode + + // Options for Table builder. + + // BloomFalsePositive is the false positive probabiltiy of bloom filter. + BloomFalsePositive float64 + + // BlockSize is the size of each block inside SSTable in bytes. + BlockSize int + + // DataKey is the key used to decrypt the encrypted text. + DataKey *pb.DataKey + + // Compression indicates the compression algorithm used for block compression. + Compression options.CompressionType + + // Block cache is used to cache decompressed and decrypted blocks. + BlockCache *ristretto.Cache + IndexCache *ristretto.Cache + + AllocPool *z.AllocatorPool + + // ZSTDCompressionLevel is the ZSTD compression level used for compressing blocks. + ZSTDCompressionLevel int +} + +// TableInterface is useful for testing. +type TableInterface interface { + Smallest() []byte + Biggest() []byte + DoesNotHave(hash uint32) bool + MaxVersion() uint64 +} + +// Table represents a loaded table file with the info we have about it. +type Table struct { + sync.Mutex + *z.MmapFile + + tableSize int // Initialized in OpenTable, using fd.Stat(). + + _index *fb.TableIndex // Nil if encryption is enabled. Use fetchIndex to access. + _cheap *cheapIndex + ref int32 // For file garbage collection. Atomic. + + // The following are initialized once and const. + smallest, biggest []byte // Smallest and largest keys (with timestamps). + id uint64 // file id, part of filename + + Checksum []byte + CreatedAt time.Time + indexStart int + indexLen int + hasBloomFilter bool + + IsInmemory bool // Set to true if the table is on level 0 and opened in memory. + opt *Options +} + +type cheapIndex struct { + MaxVersion uint64 + KeyCount uint32 + UncompressedSize uint32 + OnDiskSize uint32 + BloomFilterLength int + OffsetsLength int +} + +func (t *Table) cheapIndex() *cheapIndex { + return t._cheap +} +func (t *Table) offsetsLength() int { return t.cheapIndex().OffsetsLength } + +// MaxVersion returns the maximum version across all keys stored in this table. +func (t *Table) MaxVersion() uint64 { return t.cheapIndex().MaxVersion } + +// BloomFilterSize returns the size of the bloom filter in bytes stored in memory. +func (t *Table) BloomFilterSize() int { return t.cheapIndex().BloomFilterLength } + +// UncompressedSize is the size uncompressed data stored in this file. +func (t *Table) UncompressedSize() uint32 { return t.cheapIndex().UncompressedSize } + +// KeyCount is the total number of keys in this table. +func (t *Table) KeyCount() uint32 { return t.cheapIndex().KeyCount } + +// OnDiskSize returns the total size of key-values stored in this table (including the +// disk space occupied on the value log). +func (t *Table) OnDiskSize() uint32 { return t.cheapIndex().OnDiskSize } + +// CompressionType returns the compression algorithm used for block compression. +func (t *Table) CompressionType() options.CompressionType { + return t.opt.Compression +} + +// IncrRef increments the refcount (having to do with whether the file should be deleted) +func (t *Table) IncrRef() { + atomic.AddInt32(&t.ref, 1) +} + +// DecrRef decrements the refcount and possibly deletes the table +func (t *Table) DecrRef() error { + newRef := atomic.AddInt32(&t.ref, -1) + if newRef == 0 { + // We can safely delete this file, because for all the current files, we always have + // at least one reference pointing to them. + + // Delete all blocks from the cache. + for i := 0; i < t.offsetsLength(); i++ { + t.opt.BlockCache.Del(t.blockCacheKey(i)) + } + if err := t.Delete(); err != nil { + return err + } + } + return nil +} + +// BlockEvictHandler is used to reuse the byte slice stored in the block on cache eviction. +func BlockEvictHandler(value interface{}) { + if b, ok := value.(*block); ok { + b.decrRef() + } +} + +type block struct { + offset int + data []byte + checksum []byte + entriesIndexStart int // start index of entryOffsets list + entryOffsets []uint32 // used to binary search an entry in the block. + chkLen int // checksum length. + freeMe bool // used to determine if the blocked should be reused. + ref int32 +} + +var NumBlocks int32 + +// incrRef increments the ref of a block and return a bool indicating if the +// increment was successful. A true value indicates that the block can be used. +func (b *block) incrRef() bool { + for { + // We can't blindly add 1 to ref. We need to check whether it has + // reached zero first, because if it did, then we should absolutely not + // use this block. + ref := atomic.LoadInt32(&b.ref) + // The ref would not be equal to 0 unless the existing + // block get evicted before this line. If the ref is zero, it means that + // the block is already added the the blockPool and cannot be used + // anymore. The ref of a new block is 1 so the following condition will + // be true only if the block got reused before we could increment its + // ref. + if ref == 0 { + return false + } + // Increment the ref only if it is not zero and has not changed between + // the time we read it and we're updating it. + // + if atomic.CompareAndSwapInt32(&b.ref, ref, ref+1) { + return true + } + } +} +func (b *block) decrRef() { + if b == nil { + return + } + + // Insert the []byte into pool only if the block is resuable. When a block + // is reusable a new []byte is used for decompression and this []byte can + // be reused. + // In case of an uncompressed block, the []byte is a reference to the + // table.mmap []byte slice. Any attempt to write data to the mmap []byte + // will lead to SEGFAULT. + if atomic.AddInt32(&b.ref, -1) == 0 { + if b.freeMe { + z.Free(b.data) + } + atomic.AddInt32(&NumBlocks, -1) + // blockPool.Put(&b.data) + } + y.AssertTrue(atomic.LoadInt32(&b.ref) >= 0) +} +func (b *block) size() int64 { + return int64(3*intSize /* Size of the offset, entriesIndexStart and chkLen */ + + cap(b.data) + cap(b.checksum) + cap(b.entryOffsets)*4) +} + +func (b block) verifyCheckSum() error { + cs := &pb.Checksum{} + if err := proto.Unmarshal(b.checksum, cs); err != nil { + return y.Wrapf(err, "unable to unmarshal checksum for block") + } + return y.VerifyChecksum(b.data, cs) +} + +func CreateTable(fname string, builder *Builder) (*Table, error) { + bd := builder.Done() + mf, err := z.OpenMmapFile(fname, os.O_CREATE|os.O_RDWR|os.O_EXCL, bd.Size) + if err == z.NewFile { + // Expected. + } else if err != nil { + return nil, y.Wrapf(err, "while creating table: %s", fname) + } else { + return nil, errors.Errorf("file already exists: %s", fname) + } + + written := bd.Copy(mf.Data) + y.AssertTrue(written == len(mf.Data)) + if err := z.Msync(mf.Data); err != nil { + return nil, y.Wrapf(err, "while calling msync on %s", fname) + } + return OpenTable(mf, *builder.opts) +} + +// OpenTable assumes file has only one table and opens it. Takes ownership of fd upon function +// entry. Returns a table with one reference count on it (decrementing which may delete the file! +// -- consider t.Close() instead). The fd has to writeable because we call Truncate on it before +// deleting. Checksum for all blocks of table is verified based on value of chkMode. +func OpenTable(mf *z.MmapFile, opts Options) (*Table, error) { + // BlockSize is used to compute the approximate size of the decompressed + // block. It should not be zero if the table is compressed. + if opts.BlockSize == 0 && opts.Compression != options.None { + return nil, errors.New("Block size cannot be zero") + } + fileInfo, err := mf.Fd.Stat() + if err != nil { + mf.Close(-1) + return nil, y.Wrap(err, "") + } + + filename := fileInfo.Name() + id, ok := ParseFileID(filename) + if !ok { + mf.Close(-1) + return nil, errors.Errorf("Invalid filename: %s", filename) + } + t := &Table{ + MmapFile: mf, + ref: 1, // Caller is given one reference. + id: id, + opt: &opts, + IsInmemory: false, + tableSize: int(fileInfo.Size()), + CreatedAt: fileInfo.ModTime(), + } + + if err := t.initBiggestAndSmallest(); err != nil { + return nil, y.Wrapf(err, "failed to initialize table") + } + + if opts.ChkMode == options.OnTableRead || opts.ChkMode == options.OnTableAndBlockRead { + if err := t.VerifyChecksum(); err != nil { + mf.Close(-1) + return nil, y.Wrapf(err, "failed to verify checksum") + } + } + + return t, nil +} + +// OpenInMemoryTable is similar to OpenTable but it opens a new table from the provided data. +// OpenInMemoryTable is used for L0 tables. +func OpenInMemoryTable(data []byte, id uint64, opt *Options) (*Table, error) { + mf := &z.MmapFile{ + Data: data, + Fd: nil, + } + t := &Table{ + MmapFile: mf, + ref: 1, // Caller is given one reference. + opt: opt, + tableSize: len(data), + IsInmemory: true, + id: id, // It is important that each table gets a unique ID. + } + + if err := t.initBiggestAndSmallest(); err != nil { + return nil, err + } + return t, nil +} + +func (t *Table) initBiggestAndSmallest() error { + // This defer will help gathering debugging info incase initIndex crashes. + defer func() { + if r := recover(); r != nil { + // Use defer for printing info because there may be an intermediate panic. + var debugBuf bytes.Buffer + defer func() { + panic(fmt.Sprintf("%s\n== Recovered ==\n", debugBuf.String())) + }() + + // Get the count of null bytes at the end of file. This is to make sure if there was an + // issue with mmap sync or file copy. + count := 0 + for i := len(t.Data) - 1; i >= 0; i-- { + if t.Data[i] != 0 { + break + } + count++ + } + + fmt.Fprintf(&debugBuf, "\n== Recovering from initIndex crash ==\n") + fmt.Fprintf(&debugBuf, "File Info: [ID: %d, Size: %d, Zeros: %d]\n", + t.id, t.tableSize, count) + + fmt.Fprintf(&debugBuf, "isEnrypted: %v ", t.shouldDecrypt()) + + readPos := t.tableSize + + // Read checksum size. + readPos -= 4 + buf := t.readNoFail(readPos, 4) + checksumLen := int(y.BytesToU32(buf)) + fmt.Fprintf(&debugBuf, "checksumLen: %d ", checksumLen) + + // Read checksum. + checksum := &pb.Checksum{} + readPos -= checksumLen + buf = t.readNoFail(readPos, checksumLen) + proto.Unmarshal(buf, checksum) + fmt.Fprintf(&debugBuf, "checksum: %+v ", checksum) + + // Read index size from the footer. + readPos -= 4 + buf = t.readNoFail(readPos, 4) + indexLen := int(y.BytesToU32(buf)) + fmt.Fprintf(&debugBuf, "indexLen: %d ", indexLen) + + // Read index. + readPos -= t.indexLen + t.indexStart = readPos + indexData := t.readNoFail(readPos, t.indexLen) + fmt.Fprintf(&debugBuf, "index: %v ", indexData) + } + }() + + var err error + var ko *fb.BlockOffset + if ko, err = t.initIndex(); err != nil { + return y.Wrapf(err, "failed to read index.") + } + + t.smallest = y.Copy(ko.KeyBytes()) + + it2 := t.NewIterator(REVERSED | NOCACHE) + defer it2.Close() + it2.Rewind() + if !it2.Valid() { + return y.Wrapf(it2.err, "failed to initialize biggest for table %s", t.Filename()) + } + t.biggest = y.Copy(it2.Key()) + return nil +} + +func (t *Table) read(off, sz int) ([]byte, error) { + return t.Bytes(off, sz) +} + +func (t *Table) readNoFail(off, sz int) []byte { + res, err := t.read(off, sz) + y.Check(err) + return res +} + +// initIndex reads the index and populate the necessary table fields and returns +// first block offset +func (t *Table) initIndex() (*fb.BlockOffset, error) { + readPos := t.tableSize + + // Read checksum len from the last 4 bytes. + readPos -= 4 + buf := t.readNoFail(readPos, 4) + checksumLen := int(y.BytesToU32(buf)) + if checksumLen < 0 { + return nil, errors.New("checksum length less than zero. Data corrupted") + } + + // Read checksum. + expectedChk := &pb.Checksum{} + readPos -= checksumLen + buf = t.readNoFail(readPos, checksumLen) + if err := proto.Unmarshal(buf, expectedChk); err != nil { + return nil, err + } + + // Read index size from the footer. + readPos -= 4 + buf = t.readNoFail(readPos, 4) + t.indexLen = int(y.BytesToU32(buf)) + + // Read index. + readPos -= t.indexLen + t.indexStart = readPos + data := t.readNoFail(readPos, t.indexLen) + + if err := y.VerifyChecksum(data, expectedChk); err != nil { + return nil, y.Wrapf(err, "failed to verify checksum for table: %s", t.Filename()) + } + + index, err := t.readTableIndex() + if err != nil { + return nil, err + } + if !t.shouldDecrypt() { + // If there's no encryption, this points to the mmap'ed buffer. + t._index = index + } + t._cheap = &cheapIndex{ + MaxVersion: index.MaxVersion(), + KeyCount: index.KeyCount(), + UncompressedSize: index.UncompressedSize(), + OnDiskSize: index.OnDiskSize(), + OffsetsLength: index.OffsetsLength(), + BloomFilterLength: index.BloomFilterLength(), + } + + t.hasBloomFilter = len(index.BloomFilterBytes()) > 0 + + var bo fb.BlockOffset + y.AssertTrue(index.Offsets(&bo, 0)) + return &bo, nil +} + +// KeySplits splits the table into at least n ranges based on the block offsets. +func (t *Table) KeySplits(n int, prefix []byte) []string { + if n == 0 { + return nil + } + + oLen := t.offsetsLength() + jump := oLen / n + if jump == 0 { + jump = 1 + } + + var bo fb.BlockOffset + var res []string + for i := 0; i < oLen; i += jump { + if i >= oLen { + i = oLen - 1 + } + y.AssertTrue(t.offsets(&bo, i)) + if bytes.HasPrefix(bo.KeyBytes(), prefix) { + res = append(res, string(bo.KeyBytes())) + } + } + return res +} + +func (t *Table) fetchIndex() *fb.TableIndex { + if !t.shouldDecrypt() { + return t._index + } + + if t.opt.IndexCache == nil { + panic("Index Cache must be set for encrypted workloads") + } + if val, ok := t.opt.IndexCache.Get(t.indexKey()); ok && val != nil { + return val.(*fb.TableIndex) + } + + index, err := t.readTableIndex() + y.Check(err) + t.opt.IndexCache.Set(t.indexKey(), index, int64(t.indexLen)) + return index +} + +func (t *Table) offsets(ko *fb.BlockOffset, i int) bool { + return t.fetchIndex().Offsets(ko, i) +} + +// block function return a new block. Each block holds a ref and the byte +// slice stored in the block will be reused when the ref becomes zero. The +// caller should release the block by calling block.decrRef() on it. +func (t *Table) block(idx int, useCache bool) (*block, error) { + y.AssertTruef(idx >= 0, "idx=%d", idx) + if idx >= t.offsetsLength() { + return nil, errors.New("block out of index") + } + if t.opt.BlockCache != nil { + key := t.blockCacheKey(idx) + blk, ok := t.opt.BlockCache.Get(key) + if ok && blk != nil { + // Use the block only if the increment was successful. The block + // could get evicted from the cache between the Get() call and the + // incrRef() call. + if b := blk.(*block); b.incrRef() { + return b, nil + } + } + } + + var ko fb.BlockOffset + y.AssertTrue(t.offsets(&ko, idx)) + blk := &block{ + offset: int(ko.Offset()), + ref: 1, + } + defer blk.decrRef() // Deal with any errors, where blk would not be returned. + atomic.AddInt32(&NumBlocks, 1) + + var err error + if blk.data, err = t.read(blk.offset, int(ko.Len())); err != nil { + return nil, y.Wrapf(err, + "failed to read from file: %s at offset: %d, len: %d", + t.Fd.Name(), blk.offset, ko.Len()) + } + + if t.shouldDecrypt() { + // Decrypt the block if it is encrypted. + if blk.data, err = t.decrypt(blk.data, true); err != nil { + return nil, err + } + // blk.data is allocated via Calloc. So, do free. + blk.freeMe = true + } + + if err = t.decompress(blk); err != nil { + return nil, y.Wrapf(err, + "failed to decode compressed data in file: %s at offset: %d, len: %d", + t.Fd.Name(), blk.offset, ko.Len()) + } + + // Read meta data related to block. + readPos := len(blk.data) - 4 // First read checksum length. + blk.chkLen = int(y.BytesToU32(blk.data[readPos : readPos+4])) + + // Checksum length greater than block size could happen if the table was compressed and + // it was opened with an incorrect compression algorithm (or the data was corrupted). + if blk.chkLen > len(blk.data) { + return nil, errors.New("invalid checksum length. Either the data is " + + "corrupted or the table options are incorrectly set") + } + + // Read checksum and store it + readPos -= blk.chkLen + blk.checksum = blk.data[readPos : readPos+blk.chkLen] + // Move back and read numEntries in the block. + readPos -= 4 + numEntries := int(y.BytesToU32(blk.data[readPos : readPos+4])) + entriesIndexStart := readPos - (numEntries * 4) + entriesIndexEnd := entriesIndexStart + numEntries*4 + + blk.entryOffsets = y.BytesToU32Slice(blk.data[entriesIndexStart:entriesIndexEnd]) + + blk.entriesIndexStart = entriesIndexStart + + // Drop checksum and checksum length. + // The checksum is calculated for actual data + entry index + index length + blk.data = blk.data[:readPos+4] + + // Verify checksum on if checksum verification mode is OnRead on OnStartAndRead. + if t.opt.ChkMode == options.OnBlockRead || t.opt.ChkMode == options.OnTableAndBlockRead { + if err = blk.verifyCheckSum(); err != nil { + return nil, err + } + } + + blk.incrRef() + if useCache && t.opt.BlockCache != nil { + key := t.blockCacheKey(idx) + // incrRef should never return false here because we're calling it on a + // new block with ref=1. + y.AssertTrue(blk.incrRef()) + + // Decrement the block ref if we could not insert it in the cache. + if !t.opt.BlockCache.Set(key, blk, blk.size()) { + blk.decrRef() + } + // We have added an OnReject func in our cache, which gets called in case the block is not + // admitted to the cache. So, every block would be accounted for. + } + return blk, nil +} + +// blockCacheKey is used to store blocks in the block cache. +func (t *Table) blockCacheKey(idx int) []byte { + y.AssertTrue(t.id < math.MaxUint32) + y.AssertTrue(uint32(idx) < math.MaxUint32) + + buf := make([]byte, 8) + // Assume t.ID does not overflow uint32. + binary.BigEndian.PutUint32(buf[:4], uint32(t.ID())) + binary.BigEndian.PutUint32(buf[4:], uint32(idx)) + return buf +} + +// indexKey returns the cache key for block offsets. blockOffsets +// are stored in the index cache. +func (t *Table) indexKey() uint64 { + return t.id +} + +// IndexSize is the size of table index in bytes. +func (t *Table) IndexSize() int { + return t.indexLen +} + +// Size is its file size in bytes +func (t *Table) Size() int64 { return int64(t.tableSize) } + +// StaleDataSize is the amount of stale data (that can be dropped by a compaction )in this SST. +func (t *Table) StaleDataSize() uint32 { return t.fetchIndex().StaleDataSize() } + +// Smallest is its smallest key, or nil if there are none +func (t *Table) Smallest() []byte { return t.smallest } + +// Biggest is its biggest key, or nil if there are none +func (t *Table) Biggest() []byte { return t.biggest } + +// Filename is NOT the file name. Just kidding, it is. +func (t *Table) Filename() string { return t.Fd.Name() } + +// ID is the table's ID number (used to make the file name). +func (t *Table) ID() uint64 { return t.id } + +// DoesNotHave returns true if and only if the table does not have the key hash. +// It does a bloom filter lookup. +func (t *Table) DoesNotHave(hash uint32) bool { + if !t.hasBloomFilter { + return false + } + + y.NumLSMBloomHitsAdd(t.opt.MetricsEnabled, "DoesNotHave_ALL", 1) + index := t.fetchIndex() + bf := index.BloomFilterBytes() + mayContain := y.Filter(bf).MayContain(hash) + if !mayContain { + y.NumLSMBloomHitsAdd(t.opt.MetricsEnabled, "DoesNotHave_HIT", 1) + } + return !mayContain +} + +// readTableIndex reads table index from the sst and returns its pb format. +func (t *Table) readTableIndex() (*fb.TableIndex, error) { + data := t.readNoFail(t.indexStart, t.indexLen) + var err error + // Decrypt the table index if it is encrypted. + if t.shouldDecrypt() { + if data, err = t.decrypt(data, false); err != nil { + return nil, y.Wrapf(err, + "Error while decrypting table index for the table %d in readTableIndex", t.id) + } + } + return fb.GetRootAsTableIndex(data, 0), nil +} + +// VerifyChecksum verifies checksum for all blocks of table. This function is called by +// OpenTable() function. This function is also called inside levelsController.VerifyChecksum(). +func (t *Table) VerifyChecksum() error { + ti := t.fetchIndex() + for i := 0; i < ti.OffsetsLength(); i++ { + b, err := t.block(i, true) + if err != nil { + return y.Wrapf(err, "checksum validation failed for table: %s, block: %d, offset:%d", + t.Filename(), i, b.offset) + } + // We should not call incrRef here, because the block already has one ref when created. + defer b.decrRef() + // OnBlockRead or OnTableAndBlockRead, we don't need to call verify checksum + // on block, verification would be done while reading block itself. + if !(t.opt.ChkMode == options.OnBlockRead || t.opt.ChkMode == options.OnTableAndBlockRead) { + if err = b.verifyCheckSum(); err != nil { + return y.Wrapf(err, + "checksum validation failed for table: %s, block: %d, offset:%d", + t.Filename(), i, b.offset) + } + } + } + return nil +} + +// shouldDecrypt tells whether to decrypt or not. We decrypt only if the datakey exist +// for the table. +func (t *Table) shouldDecrypt() bool { + return t.opt.DataKey != nil +} + +// KeyID returns data key id. +func (t *Table) KeyID() uint64 { + if t.opt.DataKey != nil { + return t.opt.DataKey.KeyId + } + // By default it's 0, if it is plain text. + return 0 +} + +// decrypt decrypts the given data. It should be called only after checking shouldDecrypt. +func (t *Table) decrypt(data []byte, viaCalloc bool) ([]byte, error) { + // Last BlockSize bytes of the data is the IV. + iv := data[len(data)-aes.BlockSize:] + // Rest all bytes are data. + data = data[:len(data)-aes.BlockSize] + + var dst []byte + if viaCalloc { + dst = z.Calloc(len(data), "Table.Decrypt") + } else { + dst = make([]byte, len(data)) + } + if err := y.XORBlock(dst, data, t.opt.DataKey.Data, iv); err != nil { + return nil, y.Wrapf(err, "while decrypt") + } + return dst, nil +} + +// ParseFileID reads the file id out of a filename. +func ParseFileID(name string) (uint64, bool) { + name = filepath.Base(name) + if !strings.HasSuffix(name, fileSuffix) { + return 0, false + } + // suffix := name[len(fileSuffix):] + name = strings.TrimSuffix(name, fileSuffix) + id, err := strconv.Atoi(name) + if err != nil { + return 0, false + } + y.AssertTrue(id >= 0) + return uint64(id), true +} + +// IDToFilename does the inverse of ParseFileID +func IDToFilename(id uint64) string { + return fmt.Sprintf("%06d", id) + fileSuffix +} + +// NewFilename should be named TableFilepath -- it combines the dir with the ID to make a table +// filepath. +func NewFilename(id uint64, dir string) string { + return filepath.Join(dir, IDToFilename(id)) +} + +// decompress decompresses the data stored in a block. +func (t *Table) decompress(b *block) error { + var dst []byte + var err error + + // Point to the original b.data + src := b.data + + switch t.opt.Compression { + case options.None: + // Nothing to be done here. + return nil + case options.Snappy: + if sz, err := snappy.DecodedLen(b.data); err == nil { + dst = z.Calloc(sz, "Table.Decompress") + } else { + dst = z.Calloc(len(b.data) * 4, "Table.Decompress") // Take a guess. + } + b.data, err = snappy.Decode(dst, b.data) + if err != nil { + z.Free(dst) + return y.Wrap(err, "failed to decompress") + } + case options.ZSTD: + sz := int(float64(t.opt.BlockSize) * 1.2) + dst = z.Calloc(sz, "Table.Decompress") + b.data, err = y.ZSTDDecompress(dst, b.data) + if err != nil { + z.Free(dst) + return y.Wrap(err, "failed to decompress") + } + default: + return errors.New("Unsupported compression type") + } + + if b.freeMe == true { + z.Free(src) + b.freeMe = false + } + + if len(b.data) > 0 && len(dst) > 0 && &dst[0] != &b.data[0] { + z.Free(dst) + } else { + b.freeMe = true + } + return nil +} diff --git a/vendor/github.com/dgraph-io/badger/v3/test.sh b/vendor/github.com/dgraph-io/badger/v3/test.sh new file mode 100644 index 0000000000..6922eed0bf --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/test.sh @@ -0,0 +1,120 @@ +#!/bin/bash + +set -eo pipefail + +go version + +# Run `go list` BEFORE setting GOFLAGS so that the output is in the right +# format for grep. +# export packages because the test will run in a sub process. +export packages=$(go list ./... | grep "github.com/dgraph-io/badger/v3/") + +if [[ ! -z "$TEAMCITY_VERSION" ]]; then + export GOFLAGS="-json" +fi + +function InstallJemalloc() { + pushd . + if [ ! -f /usr/local/lib/libjemalloc.a ]; then + USER_ID=`id -u` + JEMALLOC_URL="https://github.com/jemalloc/jemalloc/releases/download/5.2.1/jemalloc-5.2.1.tar.bz2" + + mkdir -p /tmp/jemalloc-temp && cd /tmp/jemalloc-temp ; + echo "Downloading jemalloc" ; + curl -s -L ${JEMALLOC_URL} -o jemalloc.tar.bz2 ; + tar xjf ./jemalloc.tar.bz2 ; + cd jemalloc-5.2.1 ; + ./configure --with-jemalloc-prefix='je_' ; + make ; + if [ "$USER_ID" -eq "0" ]; then + make install ; + else + echo "==== Need sudo access to install jemalloc" ; + sudo make install ; + fi + fi + popd +} + +tags="-tags=jemalloc" + +# Ensure that we can compile the binary. +pushd badger +go build -v $tags . +popd + +# tags="" +InstallJemalloc + +# Run the memory intensive tests first. +manual() { + timeout="-timeout 2m" + echo "==> Running package tests for $packages" + set -e + for pkg in $packages; do + echo "===> Testing $pkg" + go test $tags -timeout=25m -race $pkg -parallel 16 + done + echo "==> DONE package tests" + + echo "==> Running manual tests" + # Run the special Truncate test. + rm -rf p + set -e + go test $tags $timeout -run='TestTruncateVlogNoClose$' --manual=true + truncate --size=4096 p/000000.vlog + go test $tags $timeout -run='TestTruncateVlogNoClose2$' --manual=true + go test $tags $timeout -run='TestTruncateVlogNoClose3$' --manual=true + rm -rf p + + # TODO(ibrahim): Let's make these tests have Manual prefix. + # go test $tags -run='TestManual' --manual=true --parallel=2 + # TestWriteBatch + # TestValueGCManaged + # TestDropPrefix + # TestDropAllManaged + go test $tags $timeout -run='TestBigKeyValuePairs$' --manual=true + go test $tags $timeout -run='TestPushValueLogLimit' --manual=true + go test $tags $timeout -run='TestKeyCount' --manual=true + go test $tags $timeout -run='TestIteratePrefix' --manual=true + go test $tags $timeout -run='TestIterateParallel' --manual=true + go test $tags $timeout -run='TestBigStream' --manual=true + go test $tags $timeout -run='TestGoroutineLeak' --manual=true + go test $tags $timeout -run='TestGetMore' --manual=true + + echo "==> DONE manual tests" +} + +root() { + # Run the normal tests. + # go test -timeout=25m -v -race github.com/dgraph-io/badger/v3/... + + echo "==> Running root level tests." + set -e + go test $tags -timeout=25m . -v -race -parallel 16 + echo "==> DONE root level tests" +} + +stream() { + set -eo pipefail + pushd badger + baseDir=$(mktemp -d -p .) + ./badger benchmark write -s --dir=$baseDir/test | tee $baseDir/log.txt + ./badger benchmark read --dir=$baseDir/test --full-scan | tee --append $baseDir/log.txt + ./badger benchmark read --dir=$baseDir/test -d=30s | tee --append $baseDir/log.txt + ./badger stream --dir=$baseDir/test -o "$baseDir/test2" | tee --append $baseDir/log.txt + count=$(cat "$baseDir/log.txt" | grep "at program end: 0 B" | wc -l) + rm -rf $baseDir + if [ $count -ne 4 ]; then + echo "LEAK detected in Badger stream." + return 1 + fi + echo "==> DONE stream test" + return 0 +} + +export -f stream +export -f manual +export -f root + +parallel --halt now,fail=1 --progress --line-buffer ::: stream manual root diff --git a/vendor/github.com/dgraph-io/badger/v3/trie/trie.go b/vendor/github.com/dgraph-io/badger/v3/trie/trie.go new file mode 100644 index 0000000000..eac974ab22 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/trie/trie.go @@ -0,0 +1,262 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 trie + +import ( + "fmt" + "strconv" + "strings" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/badger/v3/y" + "github.com/pkg/errors" +) + +type node struct { + children map[byte]*node + ignore *node + ids []uint64 +} + +func (n *node) isEmpty() bool { + return len(n.children) == 0 && len(n.ids) == 0 && n.ignore == nil +} + +func newNode() *node { + return &node{ + children: make(map[byte]*node), + ids: []uint64{}, + } +} + +// Trie datastructure. +type Trie struct { + root *node +} + +// NewTrie returns Trie. +func NewTrie() *Trie { + return &Trie{ + root: newNode(), + } +} + +// parseIgnoreBytes would parse the ignore string, and convert it into a list of bools, where +// bool[idx] = true implies that key[idx] can be ignored during comparison. +func parseIgnoreBytes(ig string) ([]bool, error) { + var out []bool + if ig == "" { + return out, nil + } + + for _, each := range strings.Split(strings.TrimSpace(ig), ",") { + r := strings.Split(strings.TrimSpace(each), "-") + if len(r) == 0 || len(r) > 2 { + return out, fmt.Errorf("Invalid range: %s", each) + } + start, end := -1, -1 + if len(r) == 2 { + idx, err := strconv.Atoi(strings.TrimSpace(r[1])) + if err != nil { + return out, err + } + end = idx + } + { + // Always consider r[0] + idx, err := strconv.Atoi(strings.TrimSpace(r[0])) + if err != nil { + return out, err + } + start = idx + } + if start == -1 { + return out, fmt.Errorf("Invalid range: %s", each) + } + for start >= len(out) { + out = append(out, false) + } + for end >= len(out) { // end could be -1, so do have the start loop above. + out = append(out, false) + } + if end == -1 { + out[start] = true + } else { + for i := start; i <= end; i++ { + out[i] = true + } + } + } + return out, nil +} + +// Add adds the id in the trie for the given prefix path. +func (t *Trie) Add(prefix []byte, id uint64) { + m := pb.Match{ + Prefix: prefix, + } + y.Check(t.AddMatch(m, id)) +} + +// AddMatch allows you to send in a prefix match, with "holes" in the prefix. The holes are +// specified via IgnoreBytes in a comma-separated list of indices starting from 0. A dash can be +// used to denote a range. Valid example is "3, 5-8, 10, 12-15". Length of IgnoreBytes does not need +// to match the length of the Prefix passed. +// +// Consider a prefix = "aaaa". If the IgnoreBytes is set to "0, 2", then along with key "aaaa...", +// a key "baba..." would also match. +func (t *Trie) AddMatch(m pb.Match, id uint64) error { + return t.fix(m, id, set) +} + +const ( + set = iota + del +) + +func (t *Trie) fix(m pb.Match, id uint64, op int) error { + curNode := t.root + + ignore, err := parseIgnoreBytes(m.IgnoreBytes) + if err != nil { + return errors.Wrapf(err, "while parsing ignore bytes: %s", m.IgnoreBytes) + } + for len(ignore) < len(m.Prefix) { + ignore = append(ignore, false) + } + for idx, byt := range m.Prefix { + var child *node + if ignore[idx] { + child = curNode.ignore + if child == nil { + if op == del { + // No valid node found for delete operation. Return immediately. + return nil + } + child = newNode() + curNode.ignore = child + } + } else { + child = curNode.children[byt] + if child == nil { + if op == del { + // No valid node found for delete operation. Return immediately. + return nil + } + child = newNode() + curNode.children[byt] = child + } + } + curNode = child + } + + // We only need to add the id to the last node of the given prefix. + if op == set { + curNode.ids = append(curNode.ids, id) + + } else if op == del { + out := curNode.ids[:0] + for _, cid := range curNode.ids { + if id != cid { + out = append(out, cid) + } + } + curNode.ids = out + } else { + y.AssertTrue(false) + } + return nil +} + +func (t *Trie) Get(key []byte) map[uint64]struct{} { + return t.get(t.root, key) +} + +// Get returns prefix matched ids for the given key. +func (t *Trie) get(curNode *node, key []byte) map[uint64]struct{} { + y.AssertTrue(curNode != nil) + + out := make(map[uint64]struct{}) + // If any node in the path of the key has ids, pick them up. + // This would also match nil prefixes. + for _, i := range curNode.ids { + out[i] = struct{}{} + } + if len(key) == 0 { + return out + } + + // If we found an ignore node, traverse that path. + if curNode.ignore != nil { + res := t.get(curNode.ignore, key[1:]) + for id := range res { + out[id] = struct{}{} + } + } + + if child := curNode.children[key[0]]; child != nil { + res := t.get(child, key[1:]) + for id := range res { + out[id] = struct{}{} + } + } + return out +} + +func removeEmpty(curNode *node) bool { + // Go depth first. + if curNode.ignore != nil { + if empty := removeEmpty(curNode.ignore); empty { + curNode.ignore = nil + } + } + + for byt, n := range curNode.children { + if empty := removeEmpty(n); empty { + delete(curNode.children, byt) + } + } + + return curNode.isEmpty() +} + +// Delete will delete the id if the id exist in the given index path. +func (t *Trie) Delete(prefix []byte, id uint64) error { + return t.DeleteMatch(pb.Match{Prefix: prefix}, id) +} + +func (t *Trie) DeleteMatch(m pb.Match, id uint64) error { + if err := t.fix(m, id, del); err != nil { + return err + } + // Would recursively delete empty nodes. + // Do not remove the t.root even if its empty. + removeEmpty(t.root) + return nil +} + +func numNodes(curNode *node) int { + if curNode == nil { + return 0 + } + + num := numNodes(curNode.ignore) + for _, n := range curNode.children { + num += numNodes(n) + } + return num + 1 +} diff --git a/vendor/github.com/dgraph-io/badger/v3/txn.go b/vendor/github.com/dgraph-io/badger/v3/txn.go new file mode 100644 index 0000000000..7ab42e60d5 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/txn.go @@ -0,0 +1,827 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bytes" + "context" + "encoding/hex" + "math" + "sort" + "strconv" + "sync" + "sync/atomic" + + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + "github.com/pkg/errors" +) + +type oracle struct { + isManaged bool // Does not change value, so no locking required. + detectConflicts bool // Determines if the txns should be checked for conflicts. + + sync.Mutex // For nextTxnTs and commits. + // writeChLock lock is for ensuring that transactions go to the write + // channel in the same order as their commit timestamps. + writeChLock sync.Mutex + nextTxnTs uint64 + + // Used to block NewTransaction, so all previous commits are visible to a new read. + txnMark *y.WaterMark + + // Either of these is used to determine which versions can be permanently + // discarded during compaction. + discardTs uint64 // Used by ManagedDB. + readMark *y.WaterMark // Used by DB. + + // committedTxns contains all committed writes (contains fingerprints + // of keys written and their latest commit counter). + committedTxns []committedTxn + lastCleanupTs uint64 + + // closer is used to stop watermarks. + closer *z.Closer +} + +type committedTxn struct { + ts uint64 + // ConflictKeys Keeps track of the entries written at timestamp ts. + conflictKeys map[uint64]struct{} +} + +func newOracle(opt Options) *oracle { + orc := &oracle{ + isManaged: opt.managedTxns, + detectConflicts: opt.DetectConflicts, + // We're not initializing nextTxnTs and readOnlyTs. It would be done after replay in Open. + // + // WaterMarks must be 64-bit aligned for atomic package, hence we must use pointers here. + // See https://golang.org/pkg/sync/atomic/#pkg-note-BUG. + readMark: &y.WaterMark{Name: "badger.PendingReads"}, + txnMark: &y.WaterMark{Name: "badger.TxnTimestamp"}, + closer: z.NewCloser(2), + } + orc.readMark.Init(orc.closer) + orc.txnMark.Init(orc.closer) + return orc +} + +func (o *oracle) Stop() { + o.closer.SignalAndWait() +} + +func (o *oracle) readTs() uint64 { + if o.isManaged { + panic("ReadTs should not be retrieved for managed DB") + } + + var readTs uint64 + o.Lock() + readTs = o.nextTxnTs - 1 + o.readMark.Begin(readTs) + o.Unlock() + + // Wait for all txns which have no conflicts, have been assigned a commit + // timestamp and are going through the write to value log and LSM tree + // process. Not waiting here could mean that some txns which have been + // committed would not be read. + y.Check(o.txnMark.WaitForMark(context.Background(), readTs)) + return readTs +} + +func (o *oracle) nextTs() uint64 { + o.Lock() + defer o.Unlock() + return o.nextTxnTs +} + +func (o *oracle) incrementNextTs() { + o.Lock() + defer o.Unlock() + o.nextTxnTs++ +} + +// Any deleted or invalid versions at or below ts would be discarded during +// compaction to reclaim disk space in LSM tree and thence value log. +func (o *oracle) setDiscardTs(ts uint64) { + o.Lock() + defer o.Unlock() + o.discardTs = ts + o.cleanupCommittedTransactions() +} + +func (o *oracle) discardAtOrBelow() uint64 { + if o.isManaged { + o.Lock() + defer o.Unlock() + return o.discardTs + } + return o.readMark.DoneUntil() +} + +// hasConflict must be called while having a lock. +func (o *oracle) hasConflict(txn *Txn) bool { + if len(txn.reads) == 0 { + return false + } + for _, committedTxn := range o.committedTxns { + // If the committedTxn.ts is less than txn.readTs that implies that the + // committedTxn finished before the current transaction started. + // We don't need to check for conflict in that case. + // This change assumes linearizability. Lack of linearizability could + // cause the read ts of a new txn to be lower than the commit ts of + // a txn before it (@mrjn). + if committedTxn.ts <= txn.readTs { + continue + } + + for _, ro := range txn.reads { + if _, has := committedTxn.conflictKeys[ro]; has { + return true + } + } + } + + return false +} + +func (o *oracle) newCommitTs(txn *Txn) (uint64, bool) { + o.Lock() + defer o.Unlock() + + if o.hasConflict(txn) { + return 0, true + } + + var ts uint64 + if !o.isManaged { + o.doneRead(txn) + o.cleanupCommittedTransactions() + + // This is the general case, when user doesn't specify the read and commit ts. + ts = o.nextTxnTs + o.nextTxnTs++ + o.txnMark.Begin(ts) + + } else { + // If commitTs is set, use it instead. + ts = txn.commitTs + } + + y.AssertTrue(ts >= o.lastCleanupTs) + + if o.detectConflicts { + // We should ensure that txns are not added to o.committedTxns slice when + // conflict detection is disabled otherwise this slice would keep growing. + o.committedTxns = append(o.committedTxns, committedTxn{ + ts: ts, + conflictKeys: txn.conflictKeys, + }) + } + + return ts, false +} + +func (o *oracle) doneRead(txn *Txn) { + if !txn.doneRead { + txn.doneRead = true + o.readMark.Done(txn.readTs) + } +} + +func (o *oracle) cleanupCommittedTransactions() { // Must be called under o.Lock + if !o.detectConflicts { + // When detectConflicts is set to false, we do not store any + // committedTxns and so there's nothing to clean up. + return + } + // Same logic as discardAtOrBelow but unlocked + var maxReadTs uint64 + if o.isManaged { + maxReadTs = o.discardTs + } else { + maxReadTs = o.readMark.DoneUntil() + } + + y.AssertTrue(maxReadTs >= o.lastCleanupTs) + + // do not run clean up if the maxReadTs (read timestamp of the + // oldest transaction that is still in flight) has not increased + if maxReadTs == o.lastCleanupTs { + return + } + o.lastCleanupTs = maxReadTs + + tmp := o.committedTxns[:0] + for _, txn := range o.committedTxns { + if txn.ts <= maxReadTs { + continue + } + tmp = append(tmp, txn) + } + o.committedTxns = tmp +} + +func (o *oracle) doneCommit(cts uint64) { + if o.isManaged { + // No need to update anything. + return + } + o.txnMark.Done(cts) +} + +// Txn represents a Badger transaction. +type Txn struct { + readTs uint64 + commitTs uint64 + size int64 + count int64 + db *DB + + reads []uint64 // contains fingerprints of keys read. + // contains fingerprints of keys written. This is used for conflict detection. + conflictKeys map[uint64]struct{} + readsLock sync.Mutex // guards the reads slice. See addReadKey. + + pendingWrites map[string]*Entry // cache stores any writes done by txn. + duplicateWrites []*Entry // Used in managed mode to store duplicate entries. + + numIterators int32 + discarded bool + doneRead bool + update bool // update is used to conditionally keep track of reads. +} + +type pendingWritesIterator struct { + entries []*Entry + nextIdx int + readTs uint64 + reversed bool +} + +func (pi *pendingWritesIterator) Next() { + pi.nextIdx++ +} + +func (pi *pendingWritesIterator) Rewind() { + pi.nextIdx = 0 +} + +func (pi *pendingWritesIterator) Seek(key []byte) { + key = y.ParseKey(key) + pi.nextIdx = sort.Search(len(pi.entries), func(idx int) bool { + cmp := bytes.Compare(pi.entries[idx].Key, key) + if !pi.reversed { + return cmp >= 0 + } + return cmp <= 0 + }) +} + +func (pi *pendingWritesIterator) Key() []byte { + y.AssertTrue(pi.Valid()) + entry := pi.entries[pi.nextIdx] + return y.KeyWithTs(entry.Key, pi.readTs) +} + +func (pi *pendingWritesIterator) Value() y.ValueStruct { + y.AssertTrue(pi.Valid()) + entry := pi.entries[pi.nextIdx] + return y.ValueStruct{ + Value: entry.Value, + Meta: entry.meta, + UserMeta: entry.UserMeta, + ExpiresAt: entry.ExpiresAt, + Version: pi.readTs, + } +} + +func (pi *pendingWritesIterator) Valid() bool { + return pi.nextIdx < len(pi.entries) +} + +func (pi *pendingWritesIterator) Close() error { + return nil +} + +func (txn *Txn) newPendingWritesIterator(reversed bool) *pendingWritesIterator { + if !txn.update || len(txn.pendingWrites) == 0 { + return nil + } + entries := make([]*Entry, 0, len(txn.pendingWrites)) + for _, e := range txn.pendingWrites { + entries = append(entries, e) + } + // Number of pending writes per transaction shouldn't be too big in general. + sort.Slice(entries, func(i, j int) bool { + cmp := bytes.Compare(entries[i].Key, entries[j].Key) + if !reversed { + return cmp < 0 + } + return cmp > 0 + }) + return &pendingWritesIterator{ + readTs: txn.readTs, + entries: entries, + reversed: reversed, + } +} + +func (txn *Txn) checkSize(e *Entry) error { + count := txn.count + 1 + // Extra bytes for the version in key. + size := txn.size + e.estimateSizeAndSetThreshold(txn.db.valueThreshold()) + 10 + if count >= txn.db.opt.maxBatchCount || size >= txn.db.opt.maxBatchSize { + return ErrTxnTooBig + } + txn.count, txn.size = count, size + return nil +} + +func exceedsSize(prefix string, max int64, key []byte) error { + return errors.Errorf("%s with size %d exceeded %d limit. %s:\n%s", + prefix, len(key), max, prefix, hex.Dump(key[:1<<10])) +} + +func (txn *Txn) modify(e *Entry) error { + const maxKeySize = 65000 + + switch { + case !txn.update: + return ErrReadOnlyTxn + case txn.discarded: + return ErrDiscardedTxn + case len(e.Key) == 0: + return ErrEmptyKey + case bytes.HasPrefix(e.Key, badgerPrefix): + return ErrInvalidKey + case len(e.Key) > maxKeySize: + // Key length can't be more than uint16, as determined by table::header. To + // keep things safe and allow badger move prefix and a timestamp suffix, let's + // cut it down to 65000, instead of using 65536. + return exceedsSize("Key", maxKeySize, e.Key) + case int64(len(e.Value)) > txn.db.opt.ValueLogFileSize: + return exceedsSize("Value", txn.db.opt.ValueLogFileSize, e.Value) + case txn.db.opt.InMemory && int64(len(e.Value)) > txn.db.valueThreshold(): + return exceedsSize("Value", txn.db.valueThreshold(), e.Value) + } + + if err := txn.db.isBanned(e.Key); err != nil { + return err + } + + if err := txn.checkSize(e); err != nil { + return err + } + + // The txn.conflictKeys is used for conflict detection. If conflict detection + // is disabled, we don't need to store key hashes in this map. + if txn.db.opt.DetectConflicts { + fp := z.MemHash(e.Key) // Avoid dealing with byte arrays. + txn.conflictKeys[fp] = struct{}{} + } + // If a duplicate entry was inserted in managed mode, move it to the duplicate writes slice. + // Add the entry to duplicateWrites only if both the entries have different versions. For + // same versions, we will overwrite the existing entry. + if oldEntry, ok := txn.pendingWrites[string(e.Key)]; ok && oldEntry.version != e.version { + txn.duplicateWrites = append(txn.duplicateWrites, oldEntry) + } + txn.pendingWrites[string(e.Key)] = e + return nil +} + +// Set adds a key-value pair to the database. +// It will return ErrReadOnlyTxn if update flag was set to false when creating the transaction. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the transaction. +func (txn *Txn) Set(key, val []byte) error { + return txn.SetEntry(NewEntry(key, val)) +} + +// SetEntry takes an Entry struct and adds the key-value pair in the struct, +// along with other metadata to the database. +// +// The current transaction keeps a reference to the entry passed in argument. +// Users must not modify the entry until the end of the transaction. +func (txn *Txn) SetEntry(e *Entry) error { + return txn.modify(e) +} + +// Delete deletes a key. +// +// This is done by adding a delete marker for the key at commit timestamp. Any +// reads happening before this timestamp would be unaffected. Any reads after +// this commit would see the deletion. +// +// The current transaction keeps a reference to the key byte slice argument. +// Users must not modify the key until the end of the transaction. +func (txn *Txn) Delete(key []byte) error { + e := &Entry{ + Key: key, + meta: bitDelete, + } + return txn.modify(e) +} + +// Get looks for key and returns corresponding Item. +// If key is not found, ErrKeyNotFound is returned. +func (txn *Txn) Get(key []byte) (item *Item, rerr error) { + if len(key) == 0 { + return nil, ErrEmptyKey + } else if txn.discarded { + return nil, ErrDiscardedTxn + } + + if err := txn.db.isBanned(key); err != nil { + return nil, err + } + + item = new(Item) + if txn.update { + if e, has := txn.pendingWrites[string(key)]; has && bytes.Equal(key, e.Key) { + if isDeletedOrExpired(e.meta, e.ExpiresAt) { + return nil, ErrKeyNotFound + } + // Fulfill from cache. + item.meta = e.meta + item.val = e.Value + item.userMeta = e.UserMeta + item.key = key + item.status = prefetched + item.version = txn.readTs + item.expiresAt = e.ExpiresAt + // We probably don't need to set db on item here. + return item, nil + } + // Only track reads if this is update txn. No need to track read if txn serviced it + // internally. + txn.addReadKey(key) + } + + seek := y.KeyWithTs(key, txn.readTs) + vs, err := txn.db.get(seek) + if err != nil { + return nil, y.Wrapf(err, "DB::Get key: %q", key) + } + if vs.Value == nil && vs.Meta == 0 { + return nil, ErrKeyNotFound + } + if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) { + return nil, ErrKeyNotFound + } + + item.key = key + item.version = vs.Version + item.meta = vs.Meta + item.userMeta = vs.UserMeta + item.vptr = y.SafeCopy(item.vptr, vs.Value) + item.txn = txn + item.expiresAt = vs.ExpiresAt + return item, nil +} + +func (txn *Txn) addReadKey(key []byte) { + if txn.update { + fp := z.MemHash(key) + + // Because of the possibility of multiple iterators it is now possible + // for multiple threads within a read-write transaction to read keys at + // the same time. The reads slice is not currently thread-safe and + // needs to be locked whenever we mark a key as read. + txn.readsLock.Lock() + txn.reads = append(txn.reads, fp) + txn.readsLock.Unlock() + } +} + +// Discard discards a created transaction. This method is very important and must be called. Commit +// method calls this internally, however, calling this multiple times doesn't cause any issues. So, +// this can safely be called via a defer right when transaction is created. +// +// NOTE: If any operations are run on a discarded transaction, ErrDiscardedTxn is returned. +func (txn *Txn) Discard() { + if txn.discarded { // Avoid a re-run. + return + } + if atomic.LoadInt32(&txn.numIterators) > 0 { + panic("Unclosed iterator at time of Txn.Discard.") + } + txn.discarded = true + if !txn.db.orc.isManaged { + txn.db.orc.doneRead(txn) + } +} + +func (txn *Txn) commitAndSend() (func() error, error) { + orc := txn.db.orc + // Ensure that the order in which we get the commit timestamp is the same as + // the order in which we push these updates to the write channel. So, we + // acquire a writeChLock before getting a commit timestamp, and only release + // it after pushing the entries to it. + orc.writeChLock.Lock() + defer orc.writeChLock.Unlock() + + commitTs, conflict := orc.newCommitTs(txn) + if conflict { + return nil, ErrConflict + } + + keepTogether := true + setVersion := func(e *Entry) { + if e.version == 0 { + e.version = commitTs + } else { + keepTogether = false + } + } + for _, e := range txn.pendingWrites { + setVersion(e) + } + // The duplicateWrites slice will be non-empty only if there are duplicate + // entries with different versions. + for _, e := range txn.duplicateWrites { + setVersion(e) + } + + entries := make([]*Entry, 0, len(txn.pendingWrites)+len(txn.duplicateWrites)+1) + + processEntry := func(e *Entry) { + // Suffix the keys with commit ts, so the key versions are sorted in + // descending order of commit timestamp. + e.Key = y.KeyWithTs(e.Key, e.version) + // Add bitTxn only if these entries are part of a transaction. We + // support SetEntryAt(..) in managed mode which means a single + // transaction can have entries with different timestamps. If entries + // in a single transaction have different timestamps, we don't add the + // transaction markers. + if keepTogether { + e.meta |= bitTxn + } + entries = append(entries, e) + } + + // The following debug information is what led to determining the cause of + // bank txn violation bug, and it took a whole bunch of effort to narrow it + // down to here. So, keep this around for at least a couple of months. + // var b strings.Builder + // fmt.Fprintf(&b, "Read: %d. Commit: %d. reads: %v. writes: %v. Keys: ", + // txn.readTs, commitTs, txn.reads, txn.conflictKeys) + for _, e := range txn.pendingWrites { + processEntry(e) + } + for _, e := range txn.duplicateWrites { + processEntry(e) + } + + if keepTogether { + // CommitTs should not be zero if we're inserting transaction markers. + y.AssertTrue(commitTs != 0) + e := &Entry{ + Key: y.KeyWithTs(txnKey, commitTs), + Value: []byte(strconv.FormatUint(commitTs, 10)), + meta: bitFinTxn, + } + entries = append(entries, e) + } + + req, err := txn.db.sendToWriteCh(entries) + if err != nil { + orc.doneCommit(commitTs) + return nil, err + } + ret := func() error { + err := req.Wait() + // Wait before marking commitTs as done. + // We can't defer doneCommit above, because it is being called from a + // callback here. + orc.doneCommit(commitTs) + return err + } + return ret, nil +} + +func (txn *Txn) commitPrecheck() error { + if txn.discarded { + return errors.New("Trying to commit a discarded txn") + } + keepTogether := true + for _, e := range txn.pendingWrites { + if e.version != 0 { + keepTogether = false + } + } + + // If keepTogether is True, it implies transaction markers will be added. + // In that case, commitTs should not be never be zero. This might happen if + // someone uses txn.Commit instead of txn.CommitAt in managed mode. This + // should happen only in managed mode. In normal mode, keepTogether will + // always be true. + if keepTogether && txn.db.opt.managedTxns && txn.commitTs == 0 { + return errors.New("CommitTs cannot be zero. Please use commitAt instead") + } + return nil +} + +// Commit commits the transaction, following these steps: +// +// 1. If there are no writes, return immediately. +// +// 2. Check if read rows were updated since txn started. If so, return ErrConflict. +// +// 3. If no conflict, generate a commit timestamp and update written rows' commit ts. +// +// 4. Batch up all writes, write them to value log and LSM tree. +// +// 5. If callback is provided, Badger will return immediately after checking +// for conflicts. Writes to the database will happen in the background. If +// there is a conflict, an error will be returned and the callback will not +// run. If there are no conflicts, the callback will be called in the +// background upon successful completion of writes or any error during write. +// +// If error is nil, the transaction is successfully committed. In case of a non-nil error, the LSM +// tree won't be updated, so there's no need for any rollback. +func (txn *Txn) Commit() error { + // txn.conflictKeys can be zero if conflict detection is turned off. So we + // should check txn.pendingWrites. + if len(txn.pendingWrites) == 0 { + return nil // Nothing to do. + } + // Precheck before discarding txn. + if err := txn.commitPrecheck(); err != nil { + return err + } + defer txn.Discard() + + txnCb, err := txn.commitAndSend() + if err != nil { + return err + } + // If batchSet failed, LSM would not have been updated. So, no need to rollback anything. + + // TODO: What if some of the txns successfully make it to value log, but others fail. + // Nothing gets updated to LSM, until a restart happens. + return txnCb() +} + +type txnCb struct { + commit func() error + user func(error) + err error +} + +func runTxnCallback(cb *txnCb) { + switch { + case cb == nil: + panic("txn callback is nil") + case cb.user == nil: + panic("Must have caught a nil callback for txn.CommitWith") + case cb.err != nil: + cb.user(cb.err) + case cb.commit != nil: + err := cb.commit() + cb.user(err) + default: + cb.user(nil) + } +} + +// CommitWith acts like Commit, but takes a callback, which gets run via a +// goroutine to avoid blocking this function. The callback is guaranteed to run, +// so it is safe to increment sync.WaitGroup before calling CommitWith, and +// decrementing it in the callback; to block until all callbacks are run. +func (txn *Txn) CommitWith(cb func(error)) { + if cb == nil { + panic("Nil callback provided to CommitWith") + } + + if len(txn.pendingWrites) == 0 { + // Do not run these callbacks from here, because the CommitWith and the + // callback might be acquiring the same locks. Instead run the callback + // from another goroutine. + go runTxnCallback(&txnCb{user: cb, err: nil}) + return + } + + // Precheck before discarding txn. + if err := txn.commitPrecheck(); err != nil { + cb(err) + return + } + + defer txn.Discard() + + commitCb, err := txn.commitAndSend() + if err != nil { + go runTxnCallback(&txnCb{user: cb, err: err}) + return + } + + go runTxnCallback(&txnCb{user: cb, commit: commitCb}) +} + +// ReadTs returns the read timestamp of the transaction. +func (txn *Txn) ReadTs() uint64 { + return txn.readTs +} + +// NewTransaction creates a new transaction. Badger supports concurrent execution of transactions, +// providing serializable snapshot isolation, avoiding write skews. Badger achieves this by tracking +// the keys read and at Commit time, ensuring that these read keys weren't concurrently modified by +// another transaction. +// +// For read-only transactions, set update to false. In this mode, we don't track the rows read for +// any changes. Thus, any long running iterations done in this mode wouldn't pay this overhead. +// +// Running transactions concurrently is OK. However, a transaction itself isn't thread safe, and +// should only be run serially. It doesn't matter if a transaction is created by one goroutine and +// passed down to other, as long as the Txn APIs are called serially. +// +// When you create a new transaction, it is absolutely essential to call +// Discard(). This should be done irrespective of what the update param is set +// to. Commit API internally runs Discard, but running it twice wouldn't cause +// any issues. +// +// txn := db.NewTransaction(false) +// defer txn.Discard() +// // Call various APIs. +func (db *DB) NewTransaction(update bool) *Txn { + return db.newTransaction(update, false) +} + +func (db *DB) newTransaction(update, isManaged bool) *Txn { + if db.opt.ReadOnly && update { + // DB is read-only, force read-only transaction. + update = false + } + + txn := &Txn{ + update: update, + db: db, + count: 1, // One extra entry for BitFin. + size: int64(len(txnKey) + 10), // Some buffer for the extra entry. + } + if update { + if db.opt.DetectConflicts { + txn.conflictKeys = make(map[uint64]struct{}) + } + txn.pendingWrites = make(map[string]*Entry) + } + if !isManaged { + txn.readTs = db.orc.readTs() + } + return txn +} + +// View executes a function creating and managing a read-only transaction for the user. Error +// returned by the function is relayed by the View method. +// If View is used with managed transactions, it would assume a read timestamp of MaxUint64. +func (db *DB) View(fn func(txn *Txn) error) error { + if db.IsClosed() { + return ErrDBClosed + } + var txn *Txn + if db.opt.managedTxns { + txn = db.NewTransactionAt(math.MaxUint64, false) + } else { + txn = db.NewTransaction(false) + } + defer txn.Discard() + + return fn(txn) +} + +// Update executes a function, creating and managing a read-write transaction +// for the user. Error returned by the function is relayed by the Update method. +// Update cannot be used with managed transactions. +func (db *DB) Update(fn func(txn *Txn) error) error { + if db.IsClosed() { + return ErrDBClosed + } + if db.opt.managedTxns { + panic("Update can only be used with managedDB=false.") + } + txn := db.NewTransaction(true) + defer txn.Discard() + + if err := fn(txn); err != nil { + return err + } + + return txn.Commit() +} diff --git a/vendor/github.com/dgraph-io/badger/v3/util.go b/vendor/github.com/dgraph-io/badger/v3/util.go new file mode 100644 index 0000000000..2e75a9ead6 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/util.go @@ -0,0 +1,117 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "encoding/hex" + "io/ioutil" + "math/rand" + "sync/atomic" + "time" + + "github.com/dgraph-io/badger/v3/table" + "github.com/dgraph-io/badger/v3/y" + "github.com/pkg/errors" +) + +func (s *levelsController) validate() error { + for _, l := range s.levels { + if err := l.validate(); err != nil { + return y.Wrap(err, "Levels Controller") + } + } + return nil +} + +// Check does some sanity check on one level of data or in-memory index. +func (s *levelHandler) validate() error { + if s.level == 0 { + return nil + } + + s.RLock() + defer s.RUnlock() + numTables := len(s.tables) + for j := 1; j < numTables; j++ { + if j >= len(s.tables) { + return errors.Errorf("Level %d, j=%d numTables=%d", s.level, j, numTables) + } + + if y.CompareKeys(s.tables[j-1].Biggest(), s.tables[j].Smallest()) >= 0 { + return errors.Errorf( + "Inter: Biggest(j-1)[%d] \n%s\n vs Smallest(j)[%d]: \n%s\n: "+ + "level=%d j=%d numTables=%d", + s.tables[j-1].ID(), hex.Dump(s.tables[j-1].Biggest()), s.tables[j].ID(), + hex.Dump(s.tables[j].Smallest()), s.level, j, numTables) + } + + if y.CompareKeys(s.tables[j].Smallest(), s.tables[j].Biggest()) > 0 { + return errors.Errorf( + "Intra: \n%s\n vs \n%s\n: level=%d j=%d numTables=%d", + hex.Dump(s.tables[j].Smallest()), hex.Dump(s.tables[j].Biggest()), s.level, j, numTables) + } + } + return nil +} + +// func (s *KV) debugPrintMore() { s.lc.debugPrintMore() } + +// // debugPrintMore shows key ranges of each level. +// func (s *levelsController) debugPrintMore() { +// s.Lock() +// defer s.Unlock() +// for i := 0; i < s.kv.opt.MaxLevels; i++ { +// s.levels[i].debugPrintMore() +// } +// } + +// func (s *levelHandler) debugPrintMore() { +// s.RLock() +// defer s.RUnlock() +// s.elog.Printf("Level %d:", s.level) +// for _, t := range s.tables { +// y.Printf(" [%s, %s]", t.Smallest(), t.Biggest()) +// } +// y.Printf("\n") +// } + +// reserveFileID reserves a unique file id. +func (s *levelsController) reserveFileID() uint64 { + id := atomic.AddUint64(&s.nextFileID, 1) + return id - 1 +} + +func getIDMap(dir string) map[uint64]struct{} { + fileInfos, err := ioutil.ReadDir(dir) + y.Check(err) + idMap := make(map[uint64]struct{}) + for _, info := range fileInfos { + if info.IsDir() { + continue + } + fileID, ok := table.ParseFileID(info.Name()) + if !ok { + continue + } + idMap[fileID] = struct{}{} + } + return idMap +} + +func init() { + rand.Seed(time.Now().UnixNano()) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/value.go b/vendor/github.com/dgraph-io/badger/v3/value.go new file mode 100644 index 0000000000..6e8f9178e2 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/value.go @@ -0,0 +1,1193 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 badger + +import ( + "bytes" + "context" + "fmt" + "hash" + "hash/crc32" + "io" + "io/ioutil" + "math" + "os" + "sort" + "strconv" + "strings" + "sync" + "sync/atomic" + + "github.com/dgraph-io/badger/v3/y" + "github.com/dgraph-io/ristretto/z" + "github.com/pkg/errors" + otrace "go.opencensus.io/trace" +) + +// maxVlogFileSize is the maximum size of the vlog file which can be created. Vlog Offset is of +// uint32, so limiting at max uint32. +var maxVlogFileSize uint32 = math.MaxUint32 + +// Values have their first byte being byteData or byteDelete. This helps us distinguish between +// a key that has never been seen and a key that has been explicitly deleted. +const ( + bitDelete byte = 1 << 0 // Set if the key has been deleted. + bitValuePointer byte = 1 << 1 // Set if the value is NOT stored directly next to key. + bitDiscardEarlierVersions byte = 1 << 2 // Set if earlier versions can be discarded. + // Set if item shouldn't be discarded via compactions (used by merge operator) + bitMergeEntry byte = 1 << 3 + // The MSB 2 bits are for transactions. + bitTxn byte = 1 << 6 // Set if the entry is part of a txn. + bitFinTxn byte = 1 << 7 // Set if the entry is to indicate end of txn in value log. + + mi int64 = 1 << 20 + + // size of vlog header. + // +----------------+------------------+ + // | keyID(8 bytes) | baseIV(12 bytes)| + // +----------------+------------------+ + vlogHeaderSize = 20 +) + +var errStop = errors.New("Stop iteration") +var errTruncate = errors.New("Do truncate") +var errDeleteVlogFile = errors.New("Delete vlog file") + +type logEntry func(e Entry, vp valuePointer) error + +type safeRead struct { + k []byte + v []byte + + recordOffset uint32 + lf *logFile +} + +// hashReader implements io.Reader, io.ByteReader interfaces. It also keeps track of the number +// bytes read. The hashReader writes to h (hash) what it reads from r. +type hashReader struct { + r io.Reader + h hash.Hash32 + bytesRead int // Number of bytes read. +} + +func newHashReader(r io.Reader) *hashReader { + hash := crc32.New(y.CastagnoliCrcTable) + return &hashReader{ + r: r, + h: hash, + } +} + +// Read reads len(p) bytes from the reader. Returns the number of bytes read, error on failure. +func (t *hashReader) Read(p []byte) (int, error) { + n, err := t.r.Read(p) + if err != nil { + return n, err + } + t.bytesRead += n + return t.h.Write(p[:n]) +} + +// ReadByte reads exactly one byte from the reader. Returns error on failure. +func (t *hashReader) ReadByte() (byte, error) { + b := make([]byte, 1) + _, err := t.Read(b) + return b[0], err +} + +// Sum32 returns the sum32 of the underlying hash. +func (t *hashReader) Sum32() uint32 { + return t.h.Sum32() +} + +// Entry reads an entry from the provided reader. It also validates the checksum for every entry +// read. Returns error on failure. +func (r *safeRead) Entry(reader io.Reader) (*Entry, error) { + tee := newHashReader(reader) + var h header + hlen, err := h.DecodeFrom(tee) + if err != nil { + return nil, err + } + if h.klen > uint32(1<<16) { // Key length must be below uint16. + return nil, errTruncate + } + kl := int(h.klen) + if cap(r.k) < kl { + r.k = make([]byte, 2*kl) + } + vl := int(h.vlen) + if cap(r.v) < vl { + r.v = make([]byte, 2*vl) + } + + e := &Entry{} + e.offset = r.recordOffset + e.hlen = hlen + buf := make([]byte, h.klen+h.vlen) + if _, err := io.ReadFull(tee, buf[:]); err != nil { + if err == io.EOF { + err = errTruncate + } + return nil, err + } + if r.lf.encryptionEnabled() { + if buf, err = r.lf.decryptKV(buf[:], r.recordOffset); err != nil { + return nil, err + } + } + e.Key = buf[:h.klen] + e.Value = buf[h.klen:] + var crcBuf [crc32.Size]byte + if _, err := io.ReadFull(reader, crcBuf[:]); err != nil { + if err == io.EOF { + err = errTruncate + } + return nil, err + } + crc := y.BytesToU32(crcBuf[:]) + if crc != tee.Sum32() { + return nil, errTruncate + } + e.meta = h.meta + e.UserMeta = h.userMeta + e.ExpiresAt = h.expiresAt + return e, nil +} + +func (vlog *valueLog) rewrite(f *logFile) error { + vlog.filesLock.RLock() + for _, fid := range vlog.filesToBeDeleted { + if fid == f.fid { + vlog.filesLock.RUnlock() + return errors.Errorf("value log file already marked for deletion fid: %d", fid) + } + } + maxFid := vlog.maxFid + y.AssertTruef(uint32(f.fid) < maxFid, "fid to move: %d. Current max fid: %d", f.fid, maxFid) + vlog.filesLock.RUnlock() + + vlog.opt.Infof("Rewriting fid: %d", f.fid) + wb := make([]*Entry, 0, 1000) + var size int64 + + y.AssertTrue(vlog.db != nil) + var count, moved int + fe := func(e Entry) error { + count++ + if count%100000 == 0 { + vlog.opt.Debugf("Processing entry %d", count) + } + + vs, err := vlog.db.get(e.Key) + if err != nil { + return err + } + if discardEntry(e, vs, vlog.db) { + return nil + } + + // Value is still present in value log. + if len(vs.Value) == 0 { + return errors.Errorf("Empty value: %+v", vs) + } + var vp valuePointer + vp.Decode(vs.Value) + + // If the entry found from the LSM Tree points to a newer vlog file, don't do anything. + if vp.Fid > f.fid { + return nil + } + // If the entry found from the LSM Tree points to an offset greater than the one + // read from vlog, don't do anything. + if vp.Offset > e.offset { + return nil + } + // If the entry read from LSM Tree and vlog file point to the same vlog file and offset, + // insert them back into the DB. + // NOTE: It might be possible that the entry read from the LSM Tree points to + // an older vlog file. See the comments in the else part. + if vp.Fid == f.fid && vp.Offset == e.offset { + moved++ + // This new entry only contains the key, and a pointer to the value. + ne := new(Entry) + // Remove only the bitValuePointer and transaction markers. We + // should keep the other bits. + ne.meta = e.meta &^ (bitValuePointer | bitTxn | bitFinTxn) + ne.UserMeta = e.UserMeta + ne.ExpiresAt = e.ExpiresAt + ne.Key = append([]byte{}, e.Key...) + ne.Value = append([]byte{}, e.Value...) + es := ne.estimateSizeAndSetThreshold(vlog.db.valueThreshold()) + // Consider size of value as well while considering the total size + // of the batch. There have been reports of high memory usage in + // rewrite because we don't consider the value size. See #1292. + es += int64(len(e.Value)) + + // Ensure length and size of wb is within transaction limits. + if int64(len(wb)+1) >= vlog.opt.maxBatchCount || + size+es >= vlog.opt.maxBatchSize { + if err := vlog.db.batchSet(wb); err != nil { + return err + } + size = 0 + wb = wb[:0] + } + wb = append(wb, ne) + size += es + } else { + // It might be possible that the entry read from LSM Tree points to + // an older vlog file. This can happen in the following situation. + // Assume DB is opened with + // numberOfVersionsToKeep=1 + // + // Now, if we have ONLY one key in the system "FOO" which has been + // updated 3 times and the same key has been garbage collected 3 + // times, we'll have 3 versions of the movekey + // for the same key "FOO". + // + // NOTE: moveKeyi is the gc'ed version of the original key with version i + // We're calling the gc'ed keys as moveKey to simplify the + // explanantion. We used to add move keys but we no longer do that. + // + // Assume we have 3 move keys in L0. + // - moveKey1 (points to vlog file 10), + // - moveKey2 (points to vlog file 14) and + // - moveKey3 (points to vlog file 15). + // + // Also, assume there is another move key "moveKey1" (points to + // vlog file 6) (this is also a move Key for key "FOO" ) on upper + // levels (let's say 3). The move key "moveKey1" on level 0 was + // inserted because vlog file 6 was GCed. + // + // Here's what the arrangement looks like + // L0 => (moveKey1 => vlog10), (moveKey2 => vlog14), (moveKey3 => vlog15) + // L1 => .... + // L2 => .... + // L3 => (moveKey1 => vlog6) + // + // When L0 compaction runs, it keeps only moveKey3 because the number of versions + // to keep is set to 1. (we've dropped moveKey1's latest version) + // + // The new arrangement of keys is + // L0 => .... + // L1 => (moveKey3 => vlog15) + // L2 => .... + // L3 => (moveKey1 => vlog6) + // + // Now if we try to GC vlog file 10, the entry read from vlog file + // will point to vlog10 but the entry read from LSM Tree will point + // to vlog6. The move key read from LSM tree will point to vlog6 + // because we've asked for version 1 of the move key. + // + // This might seem like an issue but it's not really an issue + // because the user has set the number of versions to keep to 1 and + // the latest version of moveKey points to the correct vlog file + // and offset. The stale move key on L3 will be eventually dropped + // by compaction because there is a newer versions in the upper + // levels. + } + return nil + } + + _, err := f.iterate(vlog.opt.ReadOnly, 0, func(e Entry, vp valuePointer) error { + return fe(e) + }) + if err != nil { + return err + } + + batchSize := 1024 + var loops int + for i := 0; i < len(wb); { + loops++ + if batchSize == 0 { + vlog.db.opt.Warningf("We shouldn't reach batch size of zero.") + return ErrNoRewrite + } + end := i + batchSize + if end > len(wb) { + end = len(wb) + } + if err := vlog.db.batchSet(wb[i:end]); err != nil { + if err == ErrTxnTooBig { + // Decrease the batch size to half. + batchSize = batchSize / 2 + continue + } + return err + } + i += batchSize + } + vlog.opt.Infof("Processed %d entries in %d loops", len(wb), loops) + vlog.opt.Infof("Total entries: %d. Moved: %d", count, moved) + vlog.opt.Infof("Removing fid: %d", f.fid) + var deleteFileNow bool + // Entries written to LSM. Remove the older file now. + { + vlog.filesLock.Lock() + // Just a sanity-check. + if _, ok := vlog.filesMap[f.fid]; !ok { + vlog.filesLock.Unlock() + return errors.Errorf("Unable to find fid: %d", f.fid) + } + if vlog.iteratorCount() == 0 { + delete(vlog.filesMap, f.fid) + deleteFileNow = true + } else { + vlog.filesToBeDeleted = append(vlog.filesToBeDeleted, f.fid) + } + vlog.filesLock.Unlock() + } + + if deleteFileNow { + if err := vlog.deleteLogFile(f); err != nil { + return err + } + } + return nil +} + +func (vlog *valueLog) incrIteratorCount() { + atomic.AddInt32(&vlog.numActiveIterators, 1) +} + +func (vlog *valueLog) iteratorCount() int { + return int(atomic.LoadInt32(&vlog.numActiveIterators)) +} + +func (vlog *valueLog) decrIteratorCount() error { + num := atomic.AddInt32(&vlog.numActiveIterators, -1) + if num != 0 { + return nil + } + + vlog.filesLock.Lock() + lfs := make([]*logFile, 0, len(vlog.filesToBeDeleted)) + for _, id := range vlog.filesToBeDeleted { + lfs = append(lfs, vlog.filesMap[id]) + delete(vlog.filesMap, id) + } + vlog.filesToBeDeleted = nil + vlog.filesLock.Unlock() + + for _, lf := range lfs { + if err := vlog.deleteLogFile(lf); err != nil { + return err + } + } + return nil +} + +func (vlog *valueLog) deleteLogFile(lf *logFile) error { + if lf == nil { + return nil + } + lf.lock.Lock() + defer lf.lock.Unlock() + // Delete fid from discard stats as well. + vlog.discardStats.Update(lf.fid, -1) + + return lf.Delete() +} + +func (vlog *valueLog) dropAll() (int, error) { + // If db is opened in InMemory mode, we don't need to do anything since there are no vlog files. + if vlog.db.opt.InMemory { + return 0, nil + } + // We don't want to block dropAll on any pending transactions. So, don't worry about iterator + // count. + var count int + deleteAll := func() error { + vlog.filesLock.Lock() + defer vlog.filesLock.Unlock() + for _, lf := range vlog.filesMap { + if err := vlog.deleteLogFile(lf); err != nil { + return err + } + count++ + } + vlog.filesMap = make(map[uint32]*logFile) + vlog.maxFid = 0 + return nil + } + if err := deleteAll(); err != nil { + return count, err + } + + vlog.db.opt.Infof("Value logs deleted. Creating value log file: 1") + if _, err := vlog.createVlogFile(); err != nil { // Called while writes are stopped. + return count, err + } + return count, nil +} + +func (db *DB) valueThreshold() int64 { + return atomic.LoadInt64(&db.threshold.valueThreshold) +} + +type valueLog struct { + dirPath string + + // guards our view of which files exist, which to be deleted, how many active iterators + filesLock sync.RWMutex + filesMap map[uint32]*logFile + maxFid uint32 + filesToBeDeleted []uint32 + // A refcount of iterators -- when this hits zero, we can delete the filesToBeDeleted. + numActiveIterators int32 + + db *DB + writableLogOffset uint32 // read by read, written by write. Must access via atomics. + numEntriesWritten uint32 + opt Options + + garbageCh chan struct{} + discardStats *discardStats +} + +func vlogFilePath(dirPath string, fid uint32) string { + return fmt.Sprintf("%s%s%06d.vlog", dirPath, string(os.PathSeparator), fid) +} + +func (vlog *valueLog) fpath(fid uint32) string { + return vlogFilePath(vlog.dirPath, fid) +} + +func (vlog *valueLog) populateFilesMap() error { + vlog.filesMap = make(map[uint32]*logFile) + + files, err := ioutil.ReadDir(vlog.dirPath) + if err != nil { + return errFile(err, vlog.dirPath, "Unable to open log dir.") + } + + found := make(map[uint64]struct{}) + for _, file := range files { + if !strings.HasSuffix(file.Name(), ".vlog") { + continue + } + fsz := len(file.Name()) + fid, err := strconv.ParseUint(file.Name()[:fsz-5], 10, 32) + if err != nil { + return errFile(err, file.Name(), "Unable to parse log id.") + } + if _, ok := found[fid]; ok { + return errFile(err, file.Name(), "Duplicate file found. Please delete one.") + } + found[fid] = struct{}{} + + lf := &logFile{ + fid: uint32(fid), + path: vlog.fpath(uint32(fid)), + registry: vlog.db.registry, + } + vlog.filesMap[uint32(fid)] = lf + if vlog.maxFid < uint32(fid) { + vlog.maxFid = uint32(fid) + } + } + return nil +} + +func (vlog *valueLog) createVlogFile() (*logFile, error) { + fid := vlog.maxFid + 1 + path := vlog.fpath(fid) + lf := &logFile{ + fid: fid, + path: path, + registry: vlog.db.registry, + writeAt: vlogHeaderSize, + opt: vlog.opt, + } + err := lf.open(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 2*vlog.opt.ValueLogFileSize) + if err != z.NewFile && err != nil { + return nil, err + } + + vlog.filesLock.Lock() + vlog.filesMap[fid] = lf + y.AssertTrue(vlog.maxFid < fid) + vlog.maxFid = fid + // writableLogOffset is only written by write func, by read by Read func. + // To avoid a race condition, all reads and updates to this variable must be + // done via atomics. + atomic.StoreUint32(&vlog.writableLogOffset, vlogHeaderSize) + vlog.numEntriesWritten = 0 + vlog.filesLock.Unlock() + + return lf, nil +} + +func errFile(err error, path string, msg string) error { + return fmt.Errorf("%s. Path=%s. Error=%v", msg, path, err) +} + +// init initializes the value log struct. This initialization needs to happen +// before compactions start. +func (vlog *valueLog) init(db *DB) { + vlog.opt = db.opt + vlog.db = db + // We don't need to open any vlog files or collect stats for GC if DB is opened + // in InMemory mode. InMemory mode doesn't create any files/directories on disk. + if vlog.opt.InMemory { + return + } + vlog.dirPath = vlog.opt.ValueDir + + vlog.garbageCh = make(chan struct{}, 1) // Only allow one GC at a time. + lf, err := InitDiscardStats(vlog.opt) + y.Check(err) + vlog.discardStats = lf +} + +func (vlog *valueLog) open(db *DB) error { + // We don't need to open any vlog files or collect stats for GC if DB is opened + // in InMemory mode. InMemory mode doesn't create any files/directories on disk. + if db.opt.InMemory { + return nil + } + + if err := vlog.populateFilesMap(); err != nil { + return err + } + // If no files are found, then create a new file. + if len(vlog.filesMap) == 0 { + if vlog.opt.ReadOnly { + return nil + } + _, err := vlog.createVlogFile() + return y.Wrapf(err, "Error while creating log file in valueLog.open") + } + fids := vlog.sortedFids() + for _, fid := range fids { + lf, ok := vlog.filesMap[fid] + y.AssertTrue(ok) + + // Just open in RDWR mode. This should not create a new log file. + lf.opt = vlog.opt + if err := lf.open(vlog.fpath(fid), os.O_RDWR, + 2*vlog.opt.ValueLogFileSize); err != nil { + return y.Wrapf(err, "Open existing file: %q", lf.path) + } + // We shouldn't delete the maxFid file. + if lf.size == vlogHeaderSize && fid != vlog.maxFid { + vlog.opt.Infof("Deleting empty file: %s", lf.path) + if err := lf.Delete(); err != nil { + return y.Wrapf(err, "while trying to delete empty file: %s", lf.path) + } + delete(vlog.filesMap, fid) + } + } + + if vlog.opt.ReadOnly { + return nil + } + // Now we can read the latest value log file, and see if it needs truncation. We could + // technically do this over all the value log files, but that would mean slowing down the value + // log open. + last, ok := vlog.filesMap[vlog.maxFid] + y.AssertTrue(ok) + lastOff, err := last.iterate(vlog.opt.ReadOnly, vlogHeaderSize, + func(_ Entry, vp valuePointer) error { + return nil + }) + if err != nil { + return y.Wrapf(err, "while iterating over: %s", last.path) + } + if err := last.Truncate(int64(lastOff)); err != nil { + return y.Wrapf(err, "while truncating last value log file: %s", last.path) + } + + // Don't write to the old log file. Always create a new one. + if _, err := vlog.createVlogFile(); err != nil { + return y.Wrapf(err, "Error while creating log file in valueLog.open") + } + return nil +} + +func (vlog *valueLog) Close() error { + if vlog == nil || vlog.db == nil || vlog.db.opt.InMemory { + return nil + } + + vlog.opt.Debugf("Stopping garbage collection of values.") + var err error + for id, lf := range vlog.filesMap { + lf.lock.Lock() // We won’t release the lock. + offset := int64(-1) + + if !vlog.opt.ReadOnly && id == vlog.maxFid { + offset = int64(vlog.woffset()) + } + if terr := lf.Close(offset); terr != nil && err == nil { + err = terr + } + } + if vlog.discardStats != nil { + if terr := vlog.discardStats.Close(-1); terr != nil && err == nil { + err = terr + } + } + return err +} + +// sortedFids returns the file id's not pending deletion, sorted. Assumes we have shared access to +// filesMap. +func (vlog *valueLog) sortedFids() []uint32 { + toBeDeleted := make(map[uint32]struct{}) + for _, fid := range vlog.filesToBeDeleted { + toBeDeleted[fid] = struct{}{} + } + ret := make([]uint32, 0, len(vlog.filesMap)) + for fid := range vlog.filesMap { + if _, ok := toBeDeleted[fid]; !ok { + ret = append(ret, fid) + } + } + sort.Slice(ret, func(i, j int) bool { + return ret[i] < ret[j] + }) + return ret +} + +type request struct { + // Input values + Entries []*Entry + // Output values and wait group stuff below + Ptrs []valuePointer + Wg sync.WaitGroup + Err error + ref int32 +} + +func (req *request) reset() { + req.Entries = req.Entries[:0] + req.Ptrs = req.Ptrs[:0] + req.Wg = sync.WaitGroup{} + req.Err = nil + req.ref = 0 +} + +func (req *request) IncrRef() { + atomic.AddInt32(&req.ref, 1) +} + +func (req *request) DecrRef() { + nRef := atomic.AddInt32(&req.ref, -1) + if nRef > 0 { + return + } + req.Entries = nil + requestPool.Put(req) +} + +func (req *request) Wait() error { + req.Wg.Wait() + err := req.Err + req.DecrRef() // DecrRef after writing to DB. + return err +} + +type requests []*request + +func (reqs requests) DecrRef() { + for _, req := range reqs { + req.DecrRef() + } +} + +func (reqs requests) IncrRef() { + for _, req := range reqs { + req.IncrRef() + } +} + +// sync function syncs content of latest value log file to disk. Syncing of value log directory is +// not required here as it happens every time a value log file rotation happens(check createVlogFile +// function). During rotation, previous value log file also gets synced to disk. It only syncs file +// if fid >= vlog.maxFid. In some cases such as replay(while opening db), it might be called with +// fid < vlog.maxFid. To sync irrespective of file id just call it with math.MaxUint32. +func (vlog *valueLog) sync() error { + if vlog.opt.SyncWrites || vlog.opt.InMemory { + return nil + } + + vlog.filesLock.RLock() + maxFid := vlog.maxFid + curlf := vlog.filesMap[maxFid] + // Sometimes it is possible that vlog.maxFid has been increased but file creation + // with same id is still in progress and this function is called. In those cases + // entry for the file might not be present in vlog.filesMap. + if curlf == nil { + vlog.filesLock.RUnlock() + return nil + } + curlf.lock.RLock() + vlog.filesLock.RUnlock() + + err := curlf.Sync() + curlf.lock.RUnlock() + return err +} + +func (vlog *valueLog) woffset() uint32 { + return atomic.LoadUint32(&vlog.writableLogOffset) +} + +// validateWrites will check whether the given requests can fit into 4GB vlog file. +// NOTE: 4GB is the maximum size we can create for vlog because value pointer offset is of type +// uint32. If we create more than 4GB, it will overflow uint32. So, limiting the size to 4GB. +func (vlog *valueLog) validateWrites(reqs []*request) error { + vlogOffset := uint64(vlog.woffset()) + for _, req := range reqs { + // calculate size of the request. + size := estimateRequestSize(req) + estimatedVlogOffset := vlogOffset + size + if estimatedVlogOffset > uint64(maxVlogFileSize) { + return errors.Errorf("Request size offset %d is bigger than maximum offset %d", + estimatedVlogOffset, maxVlogFileSize) + } + + if estimatedVlogOffset >= uint64(vlog.opt.ValueLogFileSize) { + // We'll create a new vlog file if the estimated offset is greater or equal to + // max vlog size. So, resetting the vlogOffset. + vlogOffset = 0 + continue + } + // Estimated vlog offset will become current vlog offset if the vlog is not rotated. + vlogOffset = estimatedVlogOffset + } + return nil +} + +// estimateRequestSize returns the size that needed to be written for the given request. +func estimateRequestSize(req *request) uint64 { + size := uint64(0) + for _, e := range req.Entries { + size += uint64(maxHeaderSize + len(e.Key) + len(e.Value) + crc32.Size) + } + return size +} + +// write is thread-unsafe by design and should not be called concurrently. +func (vlog *valueLog) write(reqs []*request) error { + if vlog.db.opt.InMemory { + return nil + } + // Validate writes before writing to vlog. Because, we don't want to partially write and return + // an error. + if err := vlog.validateWrites(reqs); err != nil { + return y.Wrapf(err, "while validating writes") + } + + vlog.filesLock.RLock() + maxFid := vlog.maxFid + curlf := vlog.filesMap[maxFid] + vlog.filesLock.RUnlock() + + defer func() { + if vlog.opt.SyncWrites { + if err := curlf.Sync(); err != nil { + vlog.opt.Errorf("Error while curlf sync: %v\n", err) + } + } + }() + + write := func(buf *bytes.Buffer) error { + if buf.Len() == 0 { + return nil + } + + n := uint32(buf.Len()) + endOffset := atomic.AddUint32(&vlog.writableLogOffset, n) + // Increase the file size if we cannot accommodate this entry. + if int(endOffset) >= len(curlf.Data) { + curlf.Truncate(int64(endOffset)) + } + + start := int(endOffset - n) + y.AssertTrue(copy(curlf.Data[start:], buf.Bytes()) == int(n)) + + atomic.StoreUint32(&curlf.size, endOffset) + return nil + } + + toDisk := func() error { + if vlog.woffset() > uint32(vlog.opt.ValueLogFileSize) || + vlog.numEntriesWritten > vlog.opt.ValueLogMaxEntries { + if err := curlf.doneWriting(vlog.woffset()); err != nil { + return err + } + + newlf, err := vlog.createVlogFile() + if err != nil { + return err + } + curlf = newlf + } + return nil + } + + buf := new(bytes.Buffer) + for i := range reqs { + b := reqs[i] + b.Ptrs = b.Ptrs[:0] + var written, bytesWritten int + valueSizes := make([]int64, 0, len(b.Entries)) + for j := range b.Entries { + buf.Reset() + + e := b.Entries[j] + valueSizes = append(valueSizes, int64(len(e.Value))) + if e.skipVlogAndSetThreshold(vlog.db.valueThreshold()) { + b.Ptrs = append(b.Ptrs, valuePointer{}) + continue + } + var p valuePointer + + p.Fid = curlf.fid + p.Offset = vlog.woffset() + + // We should not store transaction marks in the vlog file because it will never have all + // the entries in a transaction. If we store entries with transaction marks then value + // GC will not be able to iterate on the entire vlog file. + // But, we still want the entry to stay intact for the memTable WAL. So, store the meta + // in a temporary variable and reassign it after writing to the value log. + tmpMeta := e.meta + e.meta = e.meta &^ (bitTxn | bitFinTxn) + plen, err := curlf.encodeEntry(buf, e, p.Offset) // Now encode the entry into buffer. + if err != nil { + return err + } + // Restore the meta. + e.meta = tmpMeta + + p.Len = uint32(plen) + b.Ptrs = append(b.Ptrs, p) + if err := write(buf); err != nil { + return err + } + written++ + bytesWritten += buf.Len() + // No need to flush anything, we write to file directly via mmap. + } + y.NumWritesAdd(vlog.opt.MetricsEnabled, int64(written)) + y.NumBytesWrittenAdd(vlog.opt.MetricsEnabled, int64(bytesWritten)) + + vlog.numEntriesWritten += uint32(written) + vlog.db.threshold.update(valueSizes) + // We write to disk here so that all entries that are part of the same transaction are + // written to the same vlog file. + if err := toDisk(); err != nil { + return err + } + } + return toDisk() +} + +// Gets the logFile and acquires and RLock() for the mmap. You must call RUnlock on the file +// (if non-nil) +func (vlog *valueLog) getFileRLocked(vp valuePointer) (*logFile, error) { + vlog.filesLock.RLock() + defer vlog.filesLock.RUnlock() + ret, ok := vlog.filesMap[vp.Fid] + if !ok { + // log file has gone away, we can't do anything. Return. + return nil, errors.Errorf("file with ID: %d not found", vp.Fid) + } + + // Check for valid offset if we are reading from writable log. + maxFid := vlog.maxFid + // In read-only mode we don't need to check for writable offset as we are not writing anything. + // Moreover, this offset is not set in readonly mode. + if !vlog.opt.ReadOnly && vp.Fid == maxFid { + currentOffset := vlog.woffset() + if vp.Offset >= currentOffset { + return nil, errors.Errorf( + "Invalid value pointer offset: %d greater than current offset: %d", + vp.Offset, currentOffset) + } + } + + ret.lock.RLock() + return ret, nil +} + +// Read reads the value log at a given location. +// TODO: Make this read private. +func (vlog *valueLog) Read(vp valuePointer, _ *y.Slice) ([]byte, func(), error) { + buf, lf, err := vlog.readValueBytes(vp) + // log file is locked so, decide whether to lock immediately or let the caller to + // unlock it, after caller uses it. + cb := vlog.getUnlockCallback(lf) + if err != nil { + return nil, cb, err + } + + if vlog.opt.VerifyValueChecksum { + hash := crc32.New(y.CastagnoliCrcTable) + if _, err := hash.Write(buf[:len(buf)-crc32.Size]); err != nil { + runCallback(cb) + return nil, nil, y.Wrapf(err, "failed to write hash for vp %+v", vp) + } + // Fetch checksum from the end of the buffer. + checksum := buf[len(buf)-crc32.Size:] + if hash.Sum32() != y.BytesToU32(checksum) { + runCallback(cb) + return nil, nil, y.Wrapf(y.ErrChecksumMismatch, "value corrupted for vp: %+v", vp) + } + } + var h header + headerLen := h.Decode(buf) + kv := buf[headerLen:] + if lf.encryptionEnabled() { + kv, err = lf.decryptKV(kv, vp.Offset) + if err != nil { + return nil, cb, err + } + } + if uint32(len(kv)) < h.klen+h.vlen { + vlog.db.opt.Logger.Errorf("Invalid read: vp: %+v", vp) + return nil, nil, errors.Errorf("Invalid read: Len: %d read at:[%d:%d]", + len(kv), h.klen, h.klen+h.vlen) + } + return kv[h.klen : h.klen+h.vlen], cb, nil +} + +// getUnlockCallback will returns a function which unlock the logfile if the logfile is mmaped. +// otherwise, it unlock the logfile and return nil. +func (vlog *valueLog) getUnlockCallback(lf *logFile) func() { + if lf == nil { + return nil + } + return lf.lock.RUnlock +} + +// readValueBytes return vlog entry slice and read locked log file. Caller should take care of +// logFile unlocking. +func (vlog *valueLog) readValueBytes(vp valuePointer) ([]byte, *logFile, error) { + lf, err := vlog.getFileRLocked(vp) + if err != nil { + return nil, nil, err + } + + buf, err := lf.read(vp) + return buf, lf, err +} + +func (vlog *valueLog) pickLog(discardRatio float64) *logFile { + vlog.filesLock.RLock() + defer vlog.filesLock.RUnlock() + +LOOP: + // Pick a candidate that contains the largest amount of discardable data + fid, discard := vlog.discardStats.MaxDiscard() + + // MaxDiscard will return fid=0 if it doesn't have any discard data. The + // vlog files start from 1. + if fid == 0 { + vlog.opt.Debugf("No file with discard stats") + return nil + } + lf, ok := vlog.filesMap[fid] + // This file was deleted but it's discard stats increased because of compactions. The file + // doesn't exist so we don't need to do anything. Skip it and retry. + if !ok { + vlog.discardStats.Update(fid, -1) + goto LOOP + } + // We have a valid file. + fi, err := lf.Fd.Stat() + if err != nil { + vlog.opt.Errorf("Unable to get stats for value log fid: %d err: %+v", fi, err) + return nil + } + if thr := discardRatio * float64(fi.Size()); float64(discard) < thr { + vlog.opt.Debugf("Discard: %d less than threshold: %.0f for file: %s", + discard, thr, fi.Name()) + return nil + } + maxFid := atomic.LoadUint32(&vlog.maxFid) + if fid < maxFid { + vlog.opt.Infof("Found value log max discard fid: %d discard: %d\n", fid, discard) + lf, ok := vlog.filesMap[fid] + y.AssertTrue(ok) + return lf + } + + // Don't randomly pick any value log file. + return nil +} + +func discardEntry(e Entry, vs y.ValueStruct, db *DB) bool { + if vs.Version != y.ParseTs(e.Key) { + // Version not found. Discard. + return true + } + if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) { + return true + } + if (vs.Meta & bitValuePointer) == 0 { + // Key also stores the value in LSM. Discard. + return true + } + if (vs.Meta & bitFinTxn) > 0 { + // Just a txn finish entry. Discard. + return true + } + return false +} + +type reason struct { + total float64 + discard float64 + count int +} + +func (vlog *valueLog) doRunGC(lf *logFile) error { + _, span := otrace.StartSpan(context.Background(), "Badger.GC") + span.Annotatef(nil, "GC rewrite for: %v", lf.path) + defer span.End() + if err := vlog.rewrite(lf); err != nil { + return err + } + // Remove the file from discardStats. + vlog.discardStats.Update(lf.fid, -1) + return nil +} + +func (vlog *valueLog) waitOnGC(lc *z.Closer) { + defer lc.Done() + + <-lc.HasBeenClosed() // Wait for lc to be closed. + + // Block any GC in progress to finish, and don't allow any more writes to runGC by filling up + // the channel of size 1. + vlog.garbageCh <- struct{}{} +} + +func (vlog *valueLog) runGC(discardRatio float64) error { + select { + case vlog.garbageCh <- struct{}{}: + // Pick a log file for GC. + defer func() { + <-vlog.garbageCh + }() + + lf := vlog.pickLog(discardRatio) + if lf == nil { + return ErrNoRewrite + } + return vlog.doRunGC(lf) + default: + return ErrRejected + } +} + +func (vlog *valueLog) updateDiscardStats(stats map[uint32]int64) { + if vlog.opt.InMemory { + return + } + for fid, discard := range stats { + vlog.discardStats.Update(fid, discard) + } +} + +type vlogThreshold struct { + logger Logger + percentile float64 + valueThreshold int64 + valueCh chan []int64 + clearCh chan bool + closer *z.Closer + // Metrics contains a running log of statistics like amount of data stored etc. + vlMetrics *z.HistogramData +} + +func initVlogThreshold(opt *Options) *vlogThreshold { + getBounds := func() []float64 { + mxbd := opt.maxValueThreshold + mnbd := float64(opt.ValueThreshold) + y.AssertTruef(mxbd >= mnbd, "maximum threshold bound is less than the min threshold") + size := math.Min(mxbd-mnbd+1, 1024.0) + bdstp := (mxbd - mnbd) / size + bounds := make([]float64, int64(size)) + for i := range bounds { + if i == 0 { + bounds[0] = mnbd + continue + } + if i == int(size-1) { + bounds[i] = mxbd + continue + } + bounds[i] = bounds[i-1] + bdstp + } + return bounds + } + return &vlogThreshold{ + logger: opt.Logger, + percentile: opt.VLogPercentile, + valueThreshold: opt.ValueThreshold, + valueCh: make(chan []int64, 1000), + clearCh: make(chan bool, 1), + closer: z.NewCloser(1), + vlMetrics: z.NewHistogramData(getBounds()), + } +} + +func (v *vlogThreshold) Clear(opt Options) { + atomic.StoreInt64(&v.valueThreshold, opt.ValueThreshold) + v.clearCh <- true +} + +func (v *vlogThreshold) update(sizes []int64) { + v.valueCh <- sizes +} + +func (v *vlogThreshold) close() { + v.closer.SignalAndWait() +} + +func (v *vlogThreshold) listenForValueThresholdUpdate() { + defer v.closer.Done() + for { + select { + case <-v.closer.HasBeenClosed(): + return + case val := <-v.valueCh: + for _, e := range val { + v.vlMetrics.Update(e) + } + // we are making it to get Options.VlogPercentile so that values with sizes + // in range of Options.VlogPercentile will make it to the LSM tree and rest to the + // value log file. + p := int64(v.vlMetrics.Percentile(v.percentile)) + if atomic.LoadInt64(&v.valueThreshold) != p { + if v.logger != nil { + v.logger.Infof("updating value of threshold to: %d", p) + } + atomic.StoreInt64(&v.valueThreshold, p) + } + case <-v.clearCh: + v.vlMetrics.Clear() + } + } +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/bloom.go b/vendor/github.com/dgraph-io/badger/v3/y/bloom.go new file mode 100644 index 0000000000..806770bbf7 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/bloom.go @@ -0,0 +1,170 @@ +// Copyright 2013 The LevelDB-Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package y + +import "math" + +// Filter is an encoded set of []byte keys. +type Filter []byte + +func (f Filter) MayContainKey(k []byte) bool { + return f.MayContain(Hash(k)) +} + +// MayContain returns whether the filter may contain given key. False positives +// are possible, where it returns true for keys not in the original set. +func (f Filter) MayContain(h uint32) bool { + if len(f) < 2 { + return false + } + k := f[len(f)-1] + if k > 30 { + // This is reserved for potentially new encodings for short Bloom filters. + // Consider it a match. + return true + } + nBits := uint32(8 * (len(f) - 1)) + delta := h>>17 | h<<15 + for j := uint8(0); j < k; j++ { + bitPos := h % nBits + if f[bitPos/8]&(1<<(bitPos%8)) == 0 { + return false + } + h += delta + } + return true +} + +// NewFilter returns a new Bloom filter that encodes a set of []byte keys with +// the given number of bits per key, approximately. +// +// A good bitsPerKey value is 10, which yields a filter with ~ 1% false +// positive rate. +func NewFilter(keys []uint32, bitsPerKey int) Filter { + return Filter(appendFilter(nil, keys, bitsPerKey)) +} + +// BloomBitsPerKey returns the bits per key required by bloomfilter based on +// the false positive rate. +func BloomBitsPerKey(numEntries int, fp float64) int { + size := -1 * float64(numEntries) * math.Log(fp) / math.Pow(float64(0.69314718056), 2) + locs := math.Ceil(float64(0.69314718056) * size / float64(numEntries)) + return int(locs) +} + +func appendFilter(buf []byte, keys []uint32, bitsPerKey int) []byte { + if bitsPerKey < 0 { + bitsPerKey = 0 + } + // 0.69 is approximately ln(2). + k := uint32(float64(bitsPerKey) * 0.69) + if k < 1 { + k = 1 + } + if k > 30 { + k = 30 + } + + nBits := len(keys) * int(bitsPerKey) + // For small len(keys), we can see a very high false positive rate. Fix it + // by enforcing a minimum bloom filter length. + if nBits < 64 { + nBits = 64 + } + nBytes := (nBits + 7) / 8 + nBits = nBytes * 8 + buf, filter := extend(buf, nBytes+1) + + for _, h := range keys { + delta := h>>17 | h<<15 + for j := uint32(0); j < k; j++ { + bitPos := h % uint32(nBits) + filter[bitPos/8] |= 1 << (bitPos % 8) + h += delta + } + } + filter[nBytes] = uint8(k) + + return buf +} + +// extend appends n zero bytes to b. It returns the overall slice (of length +// n+len(originalB)) and the slice of n trailing zeroes. +func extend(b []byte, n int) (overall, trailer []byte) { + want := n + len(b) + if want <= cap(b) { + overall = b[:want] + trailer = overall[len(b):] + for i := range trailer { + trailer[i] = 0 + } + } else { + // Grow the capacity exponentially, with a 1KiB minimum. + c := 1024 + for c < want { + c += c / 4 + } + overall = make([]byte, want, c) + trailer = overall[len(b):] + copy(overall, b) + } + return overall, trailer +} + +// hash implements a hashing algorithm similar to the Murmur hash. +func Hash(b []byte) uint32 { + const ( + seed = 0xbc9f1d34 + m = 0xc6a4a793 + ) + h := uint32(seed) ^ uint32(len(b))*m + for ; len(b) >= 4; b = b[4:] { + h += uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 + h *= m + h ^= h >> 16 + } + switch len(b) { + case 3: + h += uint32(b[2]) << 16 + fallthrough + case 2: + h += uint32(b[1]) << 8 + fallthrough + case 1: + h += uint32(b[0]) + h *= m + h ^= h >> 24 + } + return h +} + +// FilterPolicy implements the db.FilterPolicy interface from the leveldb/db +// package. +// +// The integer value is the approximate number of bits used per key. A good +// value is 10, which yields a filter with ~ 1% false positive rate. +// +// It is valid to use the other API in this package (leveldb/bloom) without +// using this type or the leveldb/db package. + +// type FilterPolicy int + +// // Name implements the db.FilterPolicy interface. +// func (p FilterPolicy) Name() string { +// // This string looks arbitrary, but its value is written to LevelDB .ldb +// // files, and should be this exact value to be compatible with those files +// // and with the C++ LevelDB code. +// return "leveldb.BuiltinBloomFilter2" +// } + +// // AppendFilter implements the db.FilterPolicy interface. +// func (p FilterPolicy) AppendFilter(dst []byte, keys [][]byte) []byte { +// return appendFilter(dst, keys, int(p)) +// } + +// // MayContain implements the db.FilterPolicy interface. +// func (p FilterPolicy) MayContain(filter, key []byte) bool { +// return Filter(filter).MayContain(key) +// } diff --git a/vendor/github.com/dgraph-io/badger/v3/y/checksum.go b/vendor/github.com/dgraph-io/badger/v3/y/checksum.go new file mode 100644 index 0000000000..17d60c55d5 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/checksum.go @@ -0,0 +1,50 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import ( + "hash/crc32" + + "github.com/dgraph-io/badger/v3/pb" + + "github.com/cespare/xxhash" + "github.com/pkg/errors" +) + +// ErrChecksumMismatch is returned at checksum mismatch. +var ErrChecksumMismatch = errors.New("checksum mismatch") + +// CalculateChecksum calculates checksum for data using ct checksum type. +func CalculateChecksum(data []byte, ct pb.Checksum_Algorithm) uint64 { + switch ct { + case pb.Checksum_CRC32C: + return uint64(crc32.Checksum(data, CastagnoliCrcTable)) + case pb.Checksum_XXHash64: + return xxhash.Sum64(data) + default: + panic("checksum type not supported") + } +} + +// VerifyChecksum validates the checksum for the data against the given expected checksum. +func VerifyChecksum(data []byte, expected *pb.Checksum) error { + actual := CalculateChecksum(data, expected.Algo) + if actual != expected.Sum { + return Wrapf(ErrChecksumMismatch, "actual: %d, expected: %d", actual, expected.Sum) + } + return nil +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/encrypt.go b/vendor/github.com/dgraph-io/badger/v3/y/encrypt.go new file mode 100644 index 0000000000..a238fcf1b0 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/encrypt.go @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "io" +) + +// XORBlock encrypts the given data with AES and XOR's with IV. +// Can be used for both encryption and decryption. IV is of +// AES block size. +func XORBlock(dst, src, key, iv []byte) error { + block, err := aes.NewCipher(key) + if err != nil { + return err + } + stream := cipher.NewCTR(block, iv) + stream.XORKeyStream(dst, src) + return nil +} + +func XORBlockAllocate(src, key, iv []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + stream := cipher.NewCTR(block, iv) + dst := make([]byte, len(src)) + stream.XORKeyStream(dst, src) + return dst, nil +} + +func XORBlockStream(w io.Writer, src, key, iv []byte) error { + block, err := aes.NewCipher(key) + if err != nil { + return err + } + stream := cipher.NewCTR(block, iv) + sw := cipher.StreamWriter{S: stream, W: w} + _, err = io.Copy(sw, bytes.NewReader(src)) + return Wrapf(err, "XORBlockStream") +} + +// GenerateIV generates IV. +func GenerateIV() ([]byte, error) { + iv := make([]byte, aes.BlockSize) + _, err := rand.Read(iv) + return iv, err +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/error.go b/vendor/github.com/dgraph-io/badger/v3/y/error.go new file mode 100644 index 0000000000..a727a82eaa --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/error.go @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 y + +// This file contains some functions for error handling. Note that we are moving +// towards using x.Trace, i.e., rpc tracing using net/tracer. But for now, these +// functions are useful for simple checks logged on one machine. +// Some common use cases are: +// (1) You receive an error from external lib, and would like to check/log fatal. +// For this, use x.Check, x.Checkf. These will check for err != nil, which is +// more common in Go. If you want to check for boolean being true, use +// x.Assert, x.Assertf. +// (2) You receive an error from external lib, and would like to pass on with some +// stack trace information. In this case, use x.Wrap or x.Wrapf. +// (3) You want to generate a new error with stack trace info. Use x.Errorf. + +import ( + "fmt" + "log" + + "github.com/pkg/errors" +) + +var debugMode = false + +// Check logs fatal if err != nil. +func Check(err error) { + if err != nil { + log.Fatalf("%+v", Wrap(err, "")) + } +} + +// Check2 acts as convenience wrapper around Check, using the 2nd argument as error. +func Check2(_ interface{}, err error) { + Check(err) +} + +// AssertTrue asserts that b is true. Otherwise, it would log fatal. +func AssertTrue(b bool) { + if !b { + log.Fatalf("%+v", errors.Errorf("Assert failed")) + } +} + +// AssertTruef is AssertTrue with extra info. +func AssertTruef(b bool, format string, args ...interface{}) { + if !b { + log.Fatalf("%+v", errors.Errorf(format, args...)) + } +} + +// Wrap wraps errors from external lib. +func Wrap(err error, msg string) error { + if !debugMode { + if err == nil { + return nil + } + return fmt.Errorf("%s err: %+v", msg, err) + } + return errors.Wrap(err, msg) +} + +// Wrapf is Wrap with extra info. +func Wrapf(err error, format string, args ...interface{}) error { + if !debugMode { + if err == nil { + return nil + } + return fmt.Errorf(format+" error: %+v", append(args, err)...) + } + return errors.Wrapf(err, format, args...) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/event_log.go b/vendor/github.com/dgraph-io/badger/v3/y/event_log.go new file mode 100644 index 0000000000..ba9dcb1f63 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/event_log.go @@ -0,0 +1,31 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import "golang.org/x/net/trace" + +var ( + NoEventLog trace.EventLog = nilEventLog{} +) + +type nilEventLog struct{} + +func (nel nilEventLog) Printf(format string, a ...interface{}) {} + +func (nel nilEventLog) Errorf(format string, a ...interface{}) {} + +func (nel nilEventLog) Finish() {} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/file_dsync.go b/vendor/github.com/dgraph-io/badger/v3/y/file_dsync.go new file mode 100644 index 0000000000..ea4d9ab260 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/file_dsync.go @@ -0,0 +1,25 @@ +// +build !dragonfly,!freebsd,!windows,!plan9 + +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import "golang.org/x/sys/unix" + +func init() { + datasyncFileFlag = unix.O_DSYNC +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/file_nodsync.go b/vendor/github.com/dgraph-io/badger/v3/y/file_nodsync.go new file mode 100644 index 0000000000..54a2184e19 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/file_nodsync.go @@ -0,0 +1,25 @@ +// +build dragonfly freebsd windows plan9 + +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import "syscall" + +func init() { + datasyncFileFlag = syscall.O_SYNC +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/iterator.go b/vendor/github.com/dgraph-io/badger/v3/y/iterator.go new file mode 100644 index 0000000000..ef032ef4fa --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/iterator.go @@ -0,0 +1,95 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import ( + "bytes" + "encoding/binary" +) + +// ValueStruct represents the value info that can be associated with a key, but also the internal +// Meta field. +type ValueStruct struct { + Meta byte + UserMeta byte + ExpiresAt uint64 + Value []byte + + Version uint64 // This field is not serialized. Only for internal usage. +} + +func sizeVarint(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} + +// EncodedSize is the size of the ValueStruct when encoded +func (v *ValueStruct) EncodedSize() uint32 { + sz := len(v.Value) + 2 // meta, usermeta. + enc := sizeVarint(v.ExpiresAt) + return uint32(sz + enc) +} + +// Decode uses the length of the slice to infer the length of the Value field. +func (v *ValueStruct) Decode(b []byte) { + v.Meta = b[0] + v.UserMeta = b[1] + var sz int + v.ExpiresAt, sz = binary.Uvarint(b[2:]) + v.Value = b[2+sz:] +} + +// Encode expects a slice of length at least v.EncodedSize(). +func (v *ValueStruct) Encode(b []byte) uint32 { + b[0] = v.Meta + b[1] = v.UserMeta + sz := binary.PutUvarint(b[2:], v.ExpiresAt) + n := copy(b[2+sz:], v.Value) + return uint32(2 + sz + n) +} + +// EncodeTo should be kept in sync with the Encode function above. The reason +// this function exists is to avoid creating byte arrays per key-value pair in +// table/builder.go. +func (v *ValueStruct) EncodeTo(buf *bytes.Buffer) { + buf.WriteByte(v.Meta) + buf.WriteByte(v.UserMeta) + var enc [binary.MaxVarintLen64]byte + sz := binary.PutUvarint(enc[:], v.ExpiresAt) + + buf.Write(enc[:sz]) + buf.Write(v.Value) +} + +// Iterator is an interface for a basic iterator. +type Iterator interface { + Next() + Rewind() + Seek(key []byte) + Key() []byte + Value() ValueStruct + Valid() bool + + // All iterators should be closed so that file garbage collection works. + Close() error +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/metrics.go b/vendor/github.com/dgraph-io/badger/v3/y/metrics.go new file mode 100644 index 0000000000..cdae2a194a --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/metrics.go @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2017 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import ( + "expvar" +) + +var ( + // lsmSize has size of the LSM in bytes + lsmSize *expvar.Map + // vlogSize has size of the value log in bytes + vlogSize *expvar.Map + // pendingWrites tracks the number of pending writes. + pendingWrites *expvar.Map + + // These are cumulative + + // numReads has cumulative number of reads + numReads *expvar.Int + // numWrites has cumulative number of writes + numWrites *expvar.Int + // numBytesRead has cumulative number of bytes read + numBytesRead *expvar.Int + // numBytesWritten has cumulative number of bytes written + numBytesWritten *expvar.Int + // numLSMGets is number of LMS gets + numLSMGets *expvar.Map + // numLSMBloomHits is number of LMS bloom hits + numLSMBloomHits *expvar.Map + // numGets is number of gets + numGets *expvar.Int + // numPuts is number of puts + numPuts *expvar.Int + // numBlockedPuts is number of blocked puts + numBlockedPuts *expvar.Int + // numMemtableGets is number of memtable gets + numMemtableGets *expvar.Int + // numCompactionTables is the number of tables being compacted + numCompactionTables *expvar.Int +) + +// These variables are global and have cumulative values for all kv stores. +func init() { + numReads = expvar.NewInt("badger_v3_disk_reads_total") + numWrites = expvar.NewInt("badger_v3_disk_writes_total") + numBytesRead = expvar.NewInt("badger_v3_read_bytes") + numBytesWritten = expvar.NewInt("badger_v3_written_bytes") + numLSMGets = expvar.NewMap("badger_v3_lsm_level_gets_total") + numLSMBloomHits = expvar.NewMap("badger_v3_lsm_bloom_hits_total") + numGets = expvar.NewInt("badger_v3_gets_total") + numPuts = expvar.NewInt("badger_v3_puts_total") + numBlockedPuts = expvar.NewInt("badger_v3_blocked_puts_total") + numMemtableGets = expvar.NewInt("badger_v3_memtable_gets_total") + lsmSize = expvar.NewMap("badger_v3_lsm_size_bytes") + vlogSize = expvar.NewMap("badger_v3_vlog_size_bytes") + pendingWrites = expvar.NewMap("badger_v3_pending_writes_total") + numCompactionTables = expvar.NewInt("badger_v3_compactions_current") +} + +func NumReadsAdd(enabled bool, val int64) { + addInt(enabled, numReads, val) +} + +func NumWritesAdd(enabled bool, val int64) { + addInt(enabled, numWrites, val) +} + +func NumBytesReadAdd(enabled bool, val int64) { + addInt(enabled, numBytesRead, val) +} + +func NumBytesWrittenAdd(enabled bool, val int64) { + addInt(enabled, numBytesWritten, val) +} + +func NumGetsAdd(enabled bool, val int64) { + addInt(enabled, numGets, val) +} + +func NumPutsAdd(enabled bool, val int64) { + addInt(enabled, numPuts, val) +} + +func NumBlockedPutsAdd(enabled bool, val int64) { + addInt(enabled, numBlockedPuts, val) +} + +func NumMemtableGetsAdd(enabled bool, val int64) { + addInt(enabled, numMemtableGets, val) +} + +func NumCompactionTablesAdd(enabled bool, val int64) { + addInt(enabled, numCompactionTables, val) +} + +func LSMSizeSet(enabled bool, key string, val expvar.Var) { + storeToMap(enabled, lsmSize, key, val) +} + +func VlogSizeSet(enabled bool, key string, val expvar.Var) { + storeToMap(enabled, vlogSize, key, val) +} + +func PendingWritesSet(enabled bool, key string, val expvar.Var) { + storeToMap(enabled, pendingWrites, key, val) +} + +func NumLSMBloomHitsAdd(enabled bool, key string, val int64) { + addToMap(enabled, numLSMBloomHits, key, val) +} + +func NumLSMGetsAdd(enabled bool, key string, val int64) { + addToMap(enabled, numLSMGets, key, val) +} + +func LSMSizeGet(enabled bool, key string) expvar.Var { + return getFromMap(enabled, lsmSize, key) +} + +func VlogSizeGet(enabled bool, key string) expvar.Var { + return getFromMap(enabled, vlogSize, key) +} + +func addInt(enabled bool, metric *expvar.Int, val int64) { + if !enabled { + return + } + + metric.Add(val) +} + +func addToMap(enabled bool, metric *expvar.Map, key string, val int64) { + if !enabled { + return + } + + metric.Add(key, val) +} + +func storeToMap(enabled bool, metric *expvar.Map, key string, val expvar.Var) { + if !enabled { + return + } + + metric.Set(key, val) +} + +func getFromMap(enabled bool, metric *expvar.Map, key string) expvar.Var { + if !enabled { + return nil + } + + return metric.Get(key) +} \ No newline at end of file diff --git a/vendor/github.com/dgraph-io/badger/v3/y/watermark.go b/vendor/github.com/dgraph-io/badger/v3/y/watermark.go new file mode 100644 index 0000000000..1d7b9509c6 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/watermark.go @@ -0,0 +1,240 @@ +/* + * Copyright 2016-2018 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import ( + "container/heap" + "context" + "sync/atomic" + + "github.com/dgraph-io/ristretto/z" +) + +type uint64Heap []uint64 + +func (u uint64Heap) Len() int { return len(u) } +func (u uint64Heap) Less(i, j int) bool { return u[i] < u[j] } +func (u uint64Heap) Swap(i, j int) { u[i], u[j] = u[j], u[i] } +func (u *uint64Heap) Push(x interface{}) { *u = append(*u, x.(uint64)) } +func (u *uint64Heap) Pop() interface{} { + old := *u + n := len(old) + x := old[n-1] + *u = old[0 : n-1] + return x +} + +// mark contains one of more indices, along with a done boolean to indicate the +// status of the index: begin or done. It also contains waiters, who could be +// waiting for the watermark to reach >= a certain index. +type mark struct { + // Either this is an (index, waiter) pair or (index, done) or (indices, done). + index uint64 + waiter chan struct{} + indices []uint64 + done bool // Set to true if the index is done. +} + +// WaterMark is used to keep track of the minimum un-finished index. Typically, an index k becomes +// finished or "done" according to a WaterMark once Done(k) has been called +// 1. as many times as Begin(k) has, AND +// 2. a positive number of times. +// +// An index may also become "done" by calling SetDoneUntil at a time such that it is not +// inter-mingled with Begin/Done calls. +// +// Since doneUntil and lastIndex addresses are passed to sync/atomic packages, we ensure that they +// are 64-bit aligned by putting them at the beginning of the structure. +type WaterMark struct { + doneUntil uint64 + lastIndex uint64 + Name string + markCh chan mark +} + +// Init initializes a WaterMark struct. MUST be called before using it. +func (w *WaterMark) Init(closer *z.Closer) { + w.markCh = make(chan mark, 100) + go w.process(closer) +} + +// Begin sets the last index to the given value. +func (w *WaterMark) Begin(index uint64) { + atomic.StoreUint64(&w.lastIndex, index) + w.markCh <- mark{index: index, done: false} +} + +// BeginMany works like Begin but accepts multiple indices. +func (w *WaterMark) BeginMany(indices []uint64) { + atomic.StoreUint64(&w.lastIndex, indices[len(indices)-1]) + w.markCh <- mark{index: 0, indices: indices, done: false} +} + +// Done sets a single index as done. +func (w *WaterMark) Done(index uint64) { + w.markCh <- mark{index: index, done: true} +} + +// DoneMany works like Done but accepts multiple indices. +func (w *WaterMark) DoneMany(indices []uint64) { + w.markCh <- mark{index: 0, indices: indices, done: true} +} + +// DoneUntil returns the maximum index that has the property that all indices +// less than or equal to it are done. +func (w *WaterMark) DoneUntil() uint64 { + return atomic.LoadUint64(&w.doneUntil) +} + +// SetDoneUntil sets the maximum index that has the property that all indices +// less than or equal to it are done. +func (w *WaterMark) SetDoneUntil(val uint64) { + atomic.StoreUint64(&w.doneUntil, val) +} + +// LastIndex returns the last index for which Begin has been called. +func (w *WaterMark) LastIndex() uint64 { + return atomic.LoadUint64(&w.lastIndex) +} + +// WaitForMark waits until the given index is marked as done. +func (w *WaterMark) WaitForMark(ctx context.Context, index uint64) error { + if w.DoneUntil() >= index { + return nil + } + waitCh := make(chan struct{}) + w.markCh <- mark{index: index, waiter: waitCh} + + select { + case <-ctx.Done(): + return ctx.Err() + case <-waitCh: + return nil + } +} + +// process is used to process the Mark channel. This is not thread-safe, +// so only run one goroutine for process. One is sufficient, because +// all goroutine ops use purely memory and cpu. +// Each index has to emit atleast one begin watermark in serial order otherwise waiters +// can get blocked idefinitely. Example: We had an watermark at 100 and a waiter at 101, +// if no watermark is emitted at index 101 then waiter would get stuck indefinitely as it +// can't decide whether the task at 101 has decided not to emit watermark or it didn't get +// scheduled yet. +func (w *WaterMark) process(closer *z.Closer) { + defer closer.Done() + + var indices uint64Heap + // pending maps raft proposal index to the number of pending mutations for this proposal. + pending := make(map[uint64]int) + waiters := make(map[uint64][]chan struct{}) + + heap.Init(&indices) + + processOne := func(index uint64, done bool) { + // If not already done, then set. Otherwise, don't undo a done entry. + prev, present := pending[index] + if !present { + heap.Push(&indices, index) + } + + delta := 1 + if done { + delta = -1 + } + pending[index] = prev + delta + + // Update mark by going through all indices in order; and checking if they have + // been done. Stop at the first index, which isn't done. + doneUntil := w.DoneUntil() + if doneUntil > index { + AssertTruef(false, "Name: %s doneUntil: %d. Index: %d", w.Name, doneUntil, index) + } + + until := doneUntil + loops := 0 + + for len(indices) > 0 { + min := indices[0] + if done := pending[min]; done > 0 { + break // len(indices) will be > 0. + } + // Even if done is called multiple times causing it to become + // negative, we should still pop the index. + heap.Pop(&indices) + delete(pending, min) + until = min + loops++ + } + + if until != doneUntil { + AssertTrue(atomic.CompareAndSwapUint64(&w.doneUntil, doneUntil, until)) + } + + notifyAndRemove := func(idx uint64, toNotify []chan struct{}) { + for _, ch := range toNotify { + close(ch) + } + delete(waiters, idx) // Release the memory back. + } + + if until-doneUntil <= uint64(len(waiters)) { + // Issue #908 showed that if doneUntil is close to 2^60, while until is zero, this loop + // can hog up CPU just iterating over integers creating a busy-wait loop. So, only do + // this path if until - doneUntil is less than the number of waiters. + for idx := doneUntil + 1; idx <= until; idx++ { + if toNotify, ok := waiters[idx]; ok { + notifyAndRemove(idx, toNotify) + } + } + } else { + for idx, toNotify := range waiters { + if idx <= until { + notifyAndRemove(idx, toNotify) + } + } + } // end of notifying waiters. + } + + for { + select { + case <-closer.HasBeenClosed(): + return + case mark := <-w.markCh: + if mark.waiter != nil { + doneUntil := atomic.LoadUint64(&w.doneUntil) + if doneUntil >= mark.index { + close(mark.waiter) + } else { + ws, ok := waiters[mark.index] + if !ok { + waiters[mark.index] = []chan struct{}{mark.waiter} + } else { + waiters[mark.index] = append(ws, mark.waiter) + } + } + } else { + if mark.index > 0 { + processOne(mark.index, mark.done) + } + for _, index := range mark.indices { + processOne(index, mark.done) + } + } + } + } +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/y.go b/vendor/github.com/dgraph-io/badger/v3/y/y.go new file mode 100644 index 0000000000..92dfffcfd0 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/y.go @@ -0,0 +1,582 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import ( + "bytes" + "encoding/binary" + "fmt" + "hash/crc32" + "io" + "math" + "os" + "reflect" + "strconv" + "sync" + "time" + "unsafe" + + "github.com/dgraph-io/badger/v3/pb" + "github.com/dgraph-io/ristretto/z" + "github.com/pkg/errors" +) + +var ( + // ErrEOF indicates an end of file when trying to read from a memory mapped file + // and encountering the end of slice. + ErrEOF = errors.New("ErrEOF: End of file") + + // ErrCommitAfterFinish indicates that write batch commit was called after + // finish + ErrCommitAfterFinish = errors.New("Batch commit not permitted after finish") +) + +type Flags int + +const ( + // Sync indicates that O_DSYNC should be set on the underlying file, + // ensuring that data writes do not return until the data is flushed + // to disk. + Sync Flags = 1 << iota + // ReadOnly opens the underlying file on a read-only basis. + ReadOnly +) + +var ( + // This is O_DSYNC (datasync) on platforms that support it -- see file_unix.go + datasyncFileFlag = 0x0 + + // CastagnoliCrcTable is a CRC32 polynomial table + CastagnoliCrcTable = crc32.MakeTable(crc32.Castagnoli) +) + +// OpenExistingFile opens an existing file, errors if it doesn't exist. +func OpenExistingFile(filename string, flags Flags) (*os.File, error) { + openFlags := os.O_RDWR + if flags&ReadOnly != 0 { + openFlags = os.O_RDONLY + } + + if flags&Sync != 0 { + openFlags |= datasyncFileFlag + } + return os.OpenFile(filename, openFlags, 0) +} + +// CreateSyncedFile creates a new file (using O_EXCL), errors if it already existed. +func CreateSyncedFile(filename string, sync bool) (*os.File, error) { + flags := os.O_RDWR | os.O_CREATE | os.O_EXCL + if sync { + flags |= datasyncFileFlag + } + return os.OpenFile(filename, flags, 0600) +} + +// OpenSyncedFile creates the file if one doesn't exist. +func OpenSyncedFile(filename string, sync bool) (*os.File, error) { + flags := os.O_RDWR | os.O_CREATE + if sync { + flags |= datasyncFileFlag + } + return os.OpenFile(filename, flags, 0600) +} + +// OpenTruncFile opens the file with O_RDWR | O_CREATE | O_TRUNC +func OpenTruncFile(filename string, sync bool) (*os.File, error) { + flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC + if sync { + flags |= datasyncFileFlag + } + return os.OpenFile(filename, flags, 0600) +} + +// SafeCopy does append(a[:0], src...). +func SafeCopy(a, src []byte) []byte { + return append(a[:0], src...) +} + +// Copy copies a byte slice and returns the copied slice. +func Copy(a []byte) []byte { + b := make([]byte, len(a)) + copy(b, a) + return b +} + +// KeyWithTs generates a new key by appending ts to key. +func KeyWithTs(key []byte, ts uint64) []byte { + out := make([]byte, len(key)+8) + copy(out, key) + binary.BigEndian.PutUint64(out[len(key):], math.MaxUint64-ts) + return out +} + +// ParseTs parses the timestamp from the key bytes. +func ParseTs(key []byte) uint64 { + if len(key) <= 8 { + return 0 + } + return math.MaxUint64 - binary.BigEndian.Uint64(key[len(key)-8:]) +} + +// CompareKeys checks the key without timestamp and checks the timestamp if keyNoTs +// is same. +// a would be sorted higher than aa if we use bytes.compare +// All keys should have timestamp. +func CompareKeys(key1, key2 []byte) int { + if cmp := bytes.Compare(key1[:len(key1)-8], key2[:len(key2)-8]); cmp != 0 { + return cmp + } + return bytes.Compare(key1[len(key1)-8:], key2[len(key2)-8:]) +} + +// ParseKey parses the actual key from the key bytes. +func ParseKey(key []byte) []byte { + if key == nil { + return nil + } + + return key[:len(key)-8] +} + +// SameKey checks for key equality ignoring the version timestamp suffix. +func SameKey(src, dst []byte) bool { + if len(src) != len(dst) { + return false + } + return bytes.Equal(ParseKey(src), ParseKey(dst)) +} + +// Slice holds a reusable buf, will reallocate if you request a larger size than ever before. +// One problem is with n distinct sizes in random order it'll reallocate log(n) times. +type Slice struct { + buf []byte +} + +// Resize reuses the Slice's buffer (or makes a new one) and returns a slice in that buffer of +// length sz. +func (s *Slice) Resize(sz int) []byte { + if cap(s.buf) < sz { + s.buf = make([]byte, sz) + } + return s.buf[0:sz] +} + +// FixedDuration returns a string representation of the given duration with the +// hours, minutes, and seconds. +func FixedDuration(d time.Duration) string { + str := fmt.Sprintf("%02ds", int(d.Seconds())%60) + if d >= time.Minute { + str = fmt.Sprintf("%02dm", int(d.Minutes())%60) + str + } + if d >= time.Hour { + str = fmt.Sprintf("%02dh", int(d.Hours())) + str + } + return str +} + +// Throttle allows a limited number of workers to run at a time. It also +// provides a mechanism to check for errors encountered by workers and wait for +// them to finish. +type Throttle struct { + once sync.Once + wg sync.WaitGroup + ch chan struct{} + errCh chan error + finishErr error +} + +// NewThrottle creates a new throttle with a max number of workers. +func NewThrottle(max int) *Throttle { + return &Throttle{ + ch: make(chan struct{}, max), + errCh: make(chan error, max), + } +} + +// Do should be called by workers before they start working. It blocks if there +// are already maximum number of workers working. If it detects an error from +// previously Done workers, it would return it. +func (t *Throttle) Do() error { + for { + select { + case t.ch <- struct{}{}: + t.wg.Add(1) + return nil + case err := <-t.errCh: + if err != nil { + return err + } + } + } +} + +// Done should be called by workers when they finish working. They can also +// pass the error status of work done. +func (t *Throttle) Done(err error) { + if err != nil { + t.errCh <- err + } + select { + case <-t.ch: + default: + panic("Throttle Do Done mismatch") + } + t.wg.Done() +} + +// Finish waits until all workers have finished working. It would return any error passed by Done. +// If Finish is called multiple time, it will wait for workers to finish only once(first time). +// From next calls, it will return same error as found on first call. +func (t *Throttle) Finish() error { + t.once.Do(func() { + t.wg.Wait() + close(t.ch) + close(t.errCh) + for err := range t.errCh { + if err != nil { + t.finishErr = err + return + } + } + }) + + return t.finishErr +} + +// U32ToBytes converts the given Uint32 to bytes +func U32ToBytes(v uint32) []byte { + var uBuf [4]byte + binary.BigEndian.PutUint32(uBuf[:], v) + return uBuf[:] +} + +// BytesToU32 converts the given byte slice to uint32 +func BytesToU32(b []byte) uint32 { + return binary.BigEndian.Uint32(b) +} + +// U32SliceToBytes converts the given Uint32 slice to byte slice +func U32SliceToBytes(u32s []uint32) []byte { + if len(u32s) == 0 { + return nil + } + var b []byte + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + hdr.Len = len(u32s) * 4 + hdr.Cap = hdr.Len + hdr.Data = uintptr(unsafe.Pointer(&u32s[0])) + return b +} + +// BytesToU32Slice converts the given byte slice to uint32 slice +func BytesToU32Slice(b []byte) []uint32 { + if len(b) == 0 { + return nil + } + var u32s []uint32 + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u32s)) + hdr.Len = len(b) / 4 + hdr.Cap = hdr.Len + hdr.Data = uintptr(unsafe.Pointer(&b[0])) + return u32s +} + +// U64ToBytes converts the given Uint64 to bytes +func U64ToBytes(v uint64) []byte { + var uBuf [8]byte + binary.BigEndian.PutUint64(uBuf[:], v) + return uBuf[:] +} + +// BytesToU64 converts the given byte slice to uint64 +func BytesToU64(b []byte) uint64 { + return binary.BigEndian.Uint64(b) +} + +// U64SliceToBytes converts the given Uint64 slice to byte slice +func U64SliceToBytes(u64s []uint64) []byte { + if len(u64s) == 0 { + return nil + } + var b []byte + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + hdr.Len = len(u64s) * 8 + hdr.Cap = hdr.Len + hdr.Data = uintptr(unsafe.Pointer(&u64s[0])) + return b +} + +// BytesToU64Slice converts the given byte slice to uint64 slice +func BytesToU64Slice(b []byte) []uint64 { + if len(b) == 0 { + return nil + } + var u64s []uint64 + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u64s)) + hdr.Len = len(b) / 8 + hdr.Cap = hdr.Len + hdr.Data = uintptr(unsafe.Pointer(&b[0])) + return u64s +} + +// page struct contains one underlying buffer. +type page struct { + buf []byte +} + +// PageBuffer consists of many pages. A page is a wrapper over []byte. PageBuffer can act as a +// replacement of bytes.Buffer. Instead of having single underlying buffer, it has multiple +// underlying buffers. Hence it avoids any copy during relocation(as happens in bytes.Buffer). +// PageBuffer allocates memory in pages. Once a page is full, it will allocate page with double the +// size of previous page. Its function are not thread safe. +type PageBuffer struct { + pages []*page + + length int // Length of PageBuffer. + nextPageSize int // Size of next page to be allocated. +} + +// NewPageBuffer returns a new PageBuffer with first page having size pageSize. +func NewPageBuffer(pageSize int) *PageBuffer { + b := &PageBuffer{} + b.pages = append(b.pages, &page{buf: make([]byte, 0, pageSize)}) + b.nextPageSize = pageSize * 2 + return b +} + +// Write writes data to PageBuffer b. It returns number of bytes written and any error encountered. +func (b *PageBuffer) Write(data []byte) (int, error) { + dataLen := len(data) + for { + cp := b.pages[len(b.pages)-1] // Current page. + + n := copy(cp.buf[len(cp.buf):cap(cp.buf)], data) + cp.buf = cp.buf[:len(cp.buf)+n] + b.length += n + + if len(data) == n { + break + } + data = data[n:] + + b.pages = append(b.pages, &page{buf: make([]byte, 0, b.nextPageSize)}) + b.nextPageSize *= 2 + } + + return dataLen, nil +} + +// WriteByte writes data byte to PageBuffer and returns any encountered error. +func (b *PageBuffer) WriteByte(data byte) error { + _, err := b.Write([]byte{data}) + return err +} + +// Len returns length of PageBuffer. +func (b *PageBuffer) Len() int { + return b.length +} + +// pageForOffset returns pageIdx and startIdx for the offset. +func (b *PageBuffer) pageForOffset(offset int) (int, int) { + AssertTrue(offset < b.length) + + var pageIdx, startIdx, sizeNow int + for i := 0; i < len(b.pages); i++ { + cp := b.pages[i] + + if sizeNow+len(cp.buf)-1 < offset { + sizeNow += len(cp.buf) + } else { + pageIdx = i + startIdx = offset - sizeNow + break + } + } + + return pageIdx, startIdx +} + +// Truncate truncates PageBuffer to length n. +func (b *PageBuffer) Truncate(n int) { + pageIdx, startIdx := b.pageForOffset(n) + // For simplicity of the code reject extra pages. These pages can be kept. + b.pages = b.pages[:pageIdx+1] + cp := b.pages[len(b.pages)-1] + cp.buf = cp.buf[:startIdx] + b.length = n +} + +// Bytes returns whole Buffer data as single []byte. +func (b *PageBuffer) Bytes() []byte { + buf := make([]byte, b.length) + written := 0 + for i := 0; i < len(b.pages); i++ { + written += copy(buf[written:], b.pages[i].buf) + } + + return buf +} + +// WriteTo writes whole buffer to w. It returns number of bytes written and any error encountered. +func (b *PageBuffer) WriteTo(w io.Writer) (int64, error) { + written := int64(0) + for i := 0; i < len(b.pages); i++ { + n, err := w.Write(b.pages[i].buf) + written += int64(n) + if err != nil { + return written, err + } + } + + return written, nil +} + +// NewReaderAt returns a reader which starts reading from offset in page buffer. +func (b *PageBuffer) NewReaderAt(offset int) *PageBufferReader { + pageIdx, startIdx := b.pageForOffset(offset) + + return &PageBufferReader{ + buf: b, + pageIdx: pageIdx, + startIdx: startIdx, + } +} + +// PageBufferReader is a reader for PageBuffer. +type PageBufferReader struct { + buf *PageBuffer // Underlying page buffer. + pageIdx int // Idx of page from where it will start reading. + startIdx int // Idx inside page - buf.pages[pageIdx] from where it will start reading. +} + +// Read reads upto len(p) bytes. It returns number of bytes read and any error encountered. +func (r *PageBufferReader) Read(p []byte) (int, error) { + // Check if there is enough to Read. + pc := len(r.buf.pages) + + read := 0 + for r.pageIdx < pc && read < len(p) { + cp := r.buf.pages[r.pageIdx] // Current Page. + endIdx := len(cp.buf) // Last Idx up to which we can read from this page. + + n := copy(p[read:], cp.buf[r.startIdx:endIdx]) + read += n + r.startIdx += n + + // Instead of len(cp.buf), we comparing with cap(cp.buf). This ensures that we move to next + // page only when we have read all data. Reading from last page is an edge case. We don't + // want to move to next page until last page is full to its capacity. + if r.startIdx >= cap(cp.buf) { + // We should move to next page. + r.pageIdx++ + r.startIdx = 0 + continue + } + + // When last page in not full to its capacity and we have read all data up to its + // length, just break out of the loop. + if r.pageIdx == pc-1 { + break + } + } + + if read == 0 { + return read, io.EOF + } + + return read, nil +} + +const kvsz = int(unsafe.Sizeof(pb.KV{})) + +func NewKV(alloc *z.Allocator) *pb.KV { + if alloc == nil { + return &pb.KV{} + } + b := alloc.AllocateAligned(kvsz) + return (*pb.KV)(unsafe.Pointer(&b[0])) +} + +// IBytesToString converts size in bytes to human readable format. +// The code is taken from humanize library and changed to provide +// value upto custom decimal precision. +// IBytesToString(12312412, 1) -> 11.7 MiB +func IBytesToString(size uint64, precision int) string { + sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} + base := float64(1024) + if size < 10 { + return fmt.Sprintf("%d B", size) + } + e := math.Floor(math.Log(float64(size)) / math.Log(base)) + suffix := sizes[int(e)] + val := float64(size) / math.Pow(base, e) + f := "%." + strconv.Itoa(precision) + "f %s" + + return fmt.Sprintf(f, val, suffix) +} + +type RateMonitor struct { + start time.Time + lastSent uint64 + lastCapture time.Time + rates []float64 + idx int +} + +func NewRateMonitor(numSamples int) *RateMonitor { + return &RateMonitor{ + start: time.Now(), + rates: make([]float64, numSamples), + } +} + +const minRate = 0.0001 + +// Capture captures the current number of sent bytes. This number should be monotonically +// increasing. +func (rm *RateMonitor) Capture(sent uint64) { + diff := sent - rm.lastSent + dur := time.Since(rm.lastCapture) + rm.lastCapture, rm.lastSent = time.Now(), sent + + rate := float64(diff) / dur.Seconds() + if rate < minRate { + rate = minRate + } + rm.rates[rm.idx] = rate + rm.idx = (rm.idx + 1) % len(rm.rates) +} + +// Rate returns the average rate of transmission smoothed out by the number of samples. +func (rm *RateMonitor) Rate() uint64 { + var total float64 + var den float64 + for _, r := range rm.rates { + if r < minRate { + // Ignore this. We always set minRate, so this is a zero. + // Typically at the start of the rate monitor, we'd have zeros. + continue + } + total += r + den += 1.0 + } + if den < minRate { + return 0 + } + return uint64(total / den) +} diff --git a/vendor/github.com/dgraph-io/badger/v3/y/zstd.go b/vendor/github.com/dgraph-io/badger/v3/y/zstd.go new file mode 100644 index 0000000000..57018680a7 --- /dev/null +++ b/vendor/github.com/dgraph-io/badger/v3/y/zstd.go @@ -0,0 +1,64 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 y + +import ( + "sync" + + "github.com/klauspost/compress/zstd" +) + +var ( + decoder *zstd.Decoder + encoder *zstd.Encoder + + encOnce, decOnce sync.Once +) + +// ZSTDDecompress decompresses a block using ZSTD algorithm. +func ZSTDDecompress(dst, src []byte) ([]byte, error) { + decOnce.Do(func() { + var err error + decoder, err = zstd.NewReader(nil) + Check(err) + }) + return decoder.DecodeAll(src, dst[:0]) +} + +// ZSTDCompress compresses a block using ZSTD algorithm. +func ZSTDCompress(dst, src []byte, compressionLevel int) ([]byte, error) { + encOnce.Do(func() { + var err error + level := zstd.EncoderLevelFromZstd(compressionLevel) + encoder, err = zstd.NewWriter(nil, zstd.WithEncoderLevel(level)) + Check(err) + }) + return encoder.EncodeAll(src, dst[:0]), nil +} + +// ZSTDCompressBound returns the worst case size needed for a destination buffer. +// Klauspost ZSTD library does not provide any API for Compression Bound. This +// calculation is based on the DataDog ZSTD library. +// See https://pkg.go.dev/github.com/DataDog/zstd#CompressBound +func ZSTDCompressBound(srcSize int) int { + lowLimit := 128 << 10 // 128 kB + var margin int + if srcSize < lowLimit { + margin = (lowLimit - srcSize) >> 11 + } + return srcSize + (srcSize >> 8) + margin +} diff --git a/vendor/github.com/dgraph-io/ristretto/.deepsource.toml b/vendor/github.com/dgraph-io/ristretto/.deepsource.toml new file mode 100644 index 0000000000..40609eff3f --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/.deepsource.toml @@ -0,0 +1,17 @@ +version = 1 + +test_patterns = [ + '**/*_test.go' +] + +exclude_patterns = [ + +] + +[[analyzers]] +name = 'go' +enabled = true + + + [analyzers.meta] + import_path = 'github.com/dgraph-io/ristretto' diff --git a/vendor/github.com/dgraph-io/ristretto/CHANGELOG.md b/vendor/github.com/dgraph-io/ristretto/CHANGELOG.md new file mode 100644 index 0000000000..da964bc0c8 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/CHANGELOG.md @@ -0,0 +1,172 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project will adhere to [Semantic Versioning](http://semver.org/spec/v2.0.0.html) starting v1.0.0. + +## Unreleased + +## [0.1.0] - 2021-06-03 + +[0.1.0]: https://github.com/dgraph-io/ristretto/compare/v0.1.0..v0.0.3 +This release contains bug fixes and improvements to Ristretto. It also contains +major updates to the z package. The z package contains types such as Tree (B+ +tree), Buffer, Mmap file, etc. All these types are used in Badger and Dgraph to +improve performance and reduce memory requirements. + +### Changed +- Make item public. Add a new onReject call for rejected items. (#180) + +### Added +- Use z.Buffer backing for B+ tree (#268) +- expose GetTTL function (#270) +- docs(README): Ristretto is production-ready. (#267) +- Add IterateKV (#265) +- feat(super-flags): Add GetPath method in superflags (#258) +- add GetDuration to SuperFlag (#248) +- add Has, GetFloat64, and GetInt64 to SuperFlag (#247) +- move SuperFlag to Ristretto (#246) +- add SuperFlagHelp tool to generate flag help text (#251) +- allow empty defaults in SuperFlag (#254) +- add mmaped b+ tree (#207) +- Add API to allow the MaxCost of an existing cache to be updated. (#200) +- Add OnExit handler which can be used for manual memory management (#183) +- Add life expectancy histogram (#182) +- Add mechanism to wait for items to be processed. (#184) + +### Fixed +- change expiration type from int64 to time.Time (#277) +- fix(buffer): make buffer capacity atleast defaultCapacity (#273) +- Fixes for z.PersistentTree (#272) +- Initialize persistent tree correctly (#271) +- use xxhash v2 (#266) +- update comments to correctly reflect counter space usage (#189) +- enable riscv64 builds (#264) +- Switch from log to glog (#263) +- Use Fibonacci for latency numbers +- cache: fix race when clearning a cache (#261) +- Check for keys without values in superflags (#259) +- chore(perf): using tags instead of runtime callers to improve the performance of leak detection (#255) +- fix(Flags): panic on user errors (#256) +- fix SuperFlagHelp newline (#252) +- fix(arm): Fix crashing under ARMv6 due to memory mis-alignment (#239) +- Fix incorrect unit test coverage depiction (#245) +- chore(histogram): adding percentile in histogram (#241) +- fix(windows): use filepath instead of path (#244) +- fix(MmapFile): Close the fd before deleting the file (#242) +- Fixes CGO_ENABLED=0 compilation error (#240) +- fix(build): fix build on non-amd64 architectures (#238) +- fix(b+tree): Do not double the size of btree (#237) +- fix(jemalloc): Fix the stats of jemalloc (#236) +- Don't print stuff, only return strings. +- Bring memclrNoHeapPointers to z (#235) +- increase number of buffers from 32 to 64 in allocator (#234) +- Set minSize to 1MB. +- Opt(btree): Use Go memory instead of mmap files +- Opt(btree): Lightweight stats calculation +- Put padding internally to z.Buffer +- Chore(z): Add SetTmpDir API to set the temp directory (#233) +- Add a BufferFrom +- Bring z.Allocator and z.AllocatorPool back +- Fix(z.Allocator): Make Allocator use Go memory +- Updated ZeroOut to use a simple for loop. (#231) +- Add concurrency back +- Add a test to check concurrency of Allocator. +- Fix(buffer): Expose padding by z.Buffer's APIs and fix test (#222) +- AllocateSlice should Truncate if the file is not big enough (#226) +- Zero out allocations for structs now that we're reusing Allocators. +- Fix the ristretto substring +- Deal with nil z.AllocatorPool +- Create an AllocatorPool class. +- chore(btree): clean NewTree API (#225) +- fix(MmapFile): Don't error out if fileSize > sz (#224) +- feat(btree): allow option to reset btree and mmaping it to specified file. (#223) +- Use mremap on Linux instead of munmap+mmap (#221) +- Reuse pages in B+ tree (#220) +- fix(allocator): make nil allocator return go byte slice (#217) +- fix(buffer): Make padding internal to z.buffer (#216) +- chore(buffer): add a parent directory field in z.Buffer (#215) +- Make Allocator concurrent +- Fix infinite loop in allocator (#214) +- Add trim func +- Use allocator pool. Turn off freelist. +- Add freelists to Allocator to reuse. +- make DeleteBelow delete values that are less than lo (#211) +- Avoid an unnecessary Load procedure in IncrementOffset. +- Add Stats method in Btree. +- chore(script): fix local test script (#210) +- fix(btree): Increase buffer size if needed. (#209) +- chore(btree): add occupancy ratio, search benchmark and compact bug fix (#208) +- Add licenses, remove prints, and fix a bug in compact +- Add IncrementOffset API for z.buffers (#206) +- Show count when printing histogram (#201) +- Zbuffer: Add LenNoPadding and make padding 8 bytes (#204) +- Allocate Go memory in case allocator is nil. +- Add leak detection via leak build flag and fix a leak during cache.Close. +- Add some APIs for allocator and buffer +- Sync before truncation or close. +- Handle nil MmapFile for Sync. +- Public methods must not panic after Close() (#202) +- Check for RD_ONLY correctly. +- Modify MmapFile APIs +- Add a bunch of APIs around MmapFile +- Move APIs for mmapfile creation over to z package. +- Add ZeroOut func +- Add SliceOffsets +- z: Add TotalSize method on bloom filter (#197) +- Add Msync func +- Buffer: Use 256 GB mmap size instead of MaxInt64 (#198) +- Add a simple test to check next2Pow +- Improve memory performance (#195) +- Have a way to automatically mmap a growing buffer (#196) +- Introduce Mmapped buffers and Merge Sort (#194) +- Add a way to access an allocator via reference. +- Use jemalloc.a to ensure compilation with the Go binary +- Fix up a build issue with ReadMemStats +- Add ReadMemStats function (#193) +- Allocator helps allocate memory to be used by unsafe structs (#192) +- Improve histogram output +- Move Closer from y to z (#191) +- Add histogram.Mean() method (#188) +- Introduce Calloc: Manual Memory Management via jemalloc (#186) + +## [0.0.3] - 2020-07-06 + +[0.0.3]: https://github.com/dgraph-io/ristretto/compare/v0.0.2..v0.0.3 + +### Changed + +### Added + +### Fixed + +- z: use MemHashString and xxhash.Sum64String ([#153][]) +- Check conflict key before updating expiration map. ([#154][]) +- Fix race condition in Cache.Clear ([#133][]) +- Improve handling of updated items ([#168][]) +- Fix droppedSets count while updating the item ([#171][]) + +## [0.0.2] - 2020-02-24 + +[0.0.2]: https://github.com/dgraph-io/ristretto/compare/v0.0.1..v0.0.2 + +### Added + +- Sets with TTL. ([#122][]) + +### Fixed + +- Fix the way metrics are handled for deletions. ([#111][]) +- Support nil `*Cache` values in `Clear` and `Close`. ([#119][]) +- Delete item immediately. ([#113][]) +- Remove key from policy after TTL eviction. ([#130][]) + +[#111]: https://github.com/dgraph-io/ristretto/issues/111 +[#113]: https://github.com/dgraph-io/ristretto/issues/113 +[#119]: https://github.com/dgraph-io/ristretto/issues/119 +[#122]: https://github.com/dgraph-io/ristretto/issues/122 +[#130]: https://github.com/dgraph-io/ristretto/issues/130 + +## 0.0.1 + +First release. Basic cache functionality based on a LFU policy. diff --git a/vendor/github.com/dgraph-io/ristretto/LICENSE b/vendor/github.com/dgraph-io/ristretto/LICENSE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/vendor/github.com/dgraph-io/ristretto/README.md b/vendor/github.com/dgraph-io/ristretto/README.md new file mode 100644 index 0000000000..f4bb28cd75 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/README.md @@ -0,0 +1,220 @@ +# Ristretto +[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/dgraph-io/ristretto) +[![Go Report Card](https://img.shields.io/badge/go%20report-A%2B-brightgreen)](https://goreportcard.com/report/github.com/dgraph-io/ristretto) +[![Coverage](https://gocover.io/_badge/github.com/dgraph-io/ristretto)](https://gocover.io/github.com/dgraph-io/ristretto) +![Tests](https://github.com/dgraph-io/ristretto/workflows/tests/badge.svg) + +Ristretto is a fast, concurrent cache library built with a focus on performance and correctness. + +The motivation to build Ristretto comes from the need for a contention-free +cache in [Dgraph][]. + +**Use [Discuss Issues](https://discuss.dgraph.io/tags/c/issues/35/ristretto/40) for reporting issues about this repository.** + +[Dgraph]: https://github.com/dgraph-io/dgraph + +## Features + +* **High Hit Ratios** - with our unique admission/eviction policy pairing, Ristretto's performance is best in class. + * **Eviction: SampledLFU** - on par with exact LRU and better performance on Search and Database traces. + * **Admission: TinyLFU** - extra performance with little memory overhead (12 bits per counter). +* **Fast Throughput** - we use a variety of techniques for managing contention and the result is excellent throughput. +* **Cost-Based Eviction** - any large new item deemed valuable can evict multiple smaller items (cost could be anything). +* **Fully Concurrent** - you can use as many goroutines as you want with little throughput degradation. +* **Metrics** - optional performance metrics for throughput, hit ratios, and other stats. +* **Simple API** - just figure out your ideal `Config` values and you're off and running. + +## Status + +Ristretto is production-ready. See [Projects using Ristretto](#projects-using-ristretto). + +## Table of Contents + +* [Usage](#Usage) + * [Example](#Example) + * [Config](#Config) + * [NumCounters](#Config) + * [MaxCost](#Config) + * [BufferItems](#Config) + * [Metrics](#Config) + * [OnEvict](#Config) + * [KeyToHash](#Config) + * [Cost](#Config) +* [Benchmarks](#Benchmarks) + * [Hit Ratios](#Hit-Ratios) + * [Search](#Search) + * [Database](#Database) + * [Looping](#Looping) + * [CODASYL](#CODASYL) + * [Throughput](#Throughput) + * [Mixed](#Mixed) + * [Read](#Read) + * [Write](#Write) +* [Projects using Ristretto](#projects-using-ristretto) +* [FAQ](#FAQ) + +## Usage + +### Example + +```go +func main() { + cache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: 1e7, // number of keys to track frequency of (10M). + MaxCost: 1 << 30, // maximum cost of cache (1GB). + BufferItems: 64, // number of keys per Get buffer. + }) + if err != nil { + panic(err) + } + + // set a value with a cost of 1 + cache.Set("key", "value", 1) + + // wait for value to pass through buffers + time.Sleep(10 * time.Millisecond) + + value, found := cache.Get("key") + if !found { + panic("missing value") + } + fmt.Println(value) + cache.Del("key") +} +``` + +### Config + +The `Config` struct is passed to `NewCache` when creating Ristretto instances (see the example above). + +**NumCounters** `int64` + +NumCounters is the number of 4-bit access counters to keep for admission and eviction. We've seen good performance in setting this to 10x the number of items you expect to keep in the cache when full. + +For example, if you expect each item to have a cost of 1 and MaxCost is 100, set NumCounters to 1,000. Or, if you use variable cost values but expect the cache to hold around 10,000 items when full, set NumCounters to 100,000. The important thing is the *number of unique items* in the full cache, not necessarily the MaxCost value. + +**MaxCost** `int64` + +MaxCost is how eviction decisions are made. For example, if MaxCost is 100 and a new item with a cost of 1 increases total cache cost to 101, 1 item will be evicted. + +MaxCost can also be used to denote the max size in bytes. For example, if MaxCost is 1,000,000 (1MB) and the cache is full with 1,000 1KB items, a new item (that's accepted) would cause 5 1KB items to be evicted. + +MaxCost could be anything as long as it matches how you're using the cost values when calling Set. + +**BufferItems** `int64` + +BufferItems is the size of the Get buffers. The best value we've found for this is 64. + +If for some reason you see Get performance decreasing with lots of contention (you shouldn't), try increasing this value in increments of 64. This is a fine-tuning mechanism and you probably won't have to touch this. + +**Metrics** `bool` + +Metrics is true when you want real-time logging of a variety of stats. The reason this is a Config flag is because there's a 10% throughput performance overhead. + +**OnEvict** `func(hashes [2]uint64, value interface{}, cost int64)` + +OnEvict is called for every eviction. + +**KeyToHash** `func(key interface{}) [2]uint64` + +KeyToHash is the hashing algorithm used for every key. If this is nil, Ristretto has a variety of [defaults depending on the underlying interface type](https://github.com/dgraph-io/ristretto/blob/master/z/z.go#L19-L41). + +Note that if you want 128bit hashes you should use the full `[2]uint64`, +otherwise just fill the `uint64` at the `0` position and it will behave like +any 64bit hash. + +**Cost** `func(value interface{}) int64` + +Cost is an optional function you can pass to the Config in order to evaluate +item cost at runtime, and only for the Set calls that aren't dropped (this is +useful if calculating item cost is particularly expensive and you don't want to +waste time on items that will be dropped anyways). + +To signal to Ristretto that you'd like to use this Cost function: + +1. Set the Cost field to a non-nil function. +2. When calling Set for new items or item updates, use a `cost` of 0. + +## Benchmarks + +The benchmarks can be found in https://github.com/dgraph-io/benchmarks/tree/master/cachebench/ristretto. + +### Hit Ratios + +#### Search + +This trace is described as "disk read accesses initiated by a large commercial +search engine in response to various web search requests." + +

+ +

+ +#### Database + +This trace is described as "a database server running at a commercial site +running an ERP application on top of a commercial database." + +

+ +

+ +#### Looping + +This trace demonstrates a looping access pattern. + +

+ +

+ +#### CODASYL + +This trace is described as "references to a CODASYL database for a one hour +period." + +

+ +

+ +### Throughput + +All throughput benchmarks were ran on an Intel Core i7-8700K (3.7GHz) with 16gb +of RAM. + +#### Mixed + +

+ +

+ +#### Read + +

+ +

+ +#### Write + +

+ +

+ +## Projects Using Ristretto + +Below is a list of known projects that use Ristretto: + +- [Badger](https://github.com/dgraph-io/badger) - Embeddable key-value DB in Go +- [Dgraph](https://github.com/dgraph-io/dgraph) - Horizontally scalable and distributed GraphQL database with a graph backend +- [Vitess](https://github.com/vitessio/vitess) - database clustering system for horizontal scaling of MySQL + +## FAQ + +### How are you achieving this performance? What shortcuts are you taking? + +We go into detail in the [Ristretto blog post](https://blog.dgraph.io/post/introducing-ristretto-high-perf-go-cache/), but in short: our throughput performance can be attributed to a mix of batching and eventual consistency. Our hit ratio performance is mostly due to an excellent [admission policy](https://arxiv.org/abs/1512.00727) and SampledLFU eviction policy. + +As for "shortcuts," the only thing Ristretto does that could be construed as one is dropping some Set calls. That means a Set call for a new item (updates are guaranteed) isn't guaranteed to make it into the cache. The new item could be dropped at two points: when passing through the Set buffer or when passing through the admission policy. However, this doesn't affect hit ratios much at all as we expect the most popular items to be Set multiple times and eventually make it in the cache. + +### Is Ristretto distributed? + +No, it's just like any other Go library that you can import into your project and use in a single process. diff --git a/vendor/github.com/dgraph-io/ristretto/cache.go b/vendor/github.com/dgraph-io/ristretto/cache.go new file mode 100644 index 0000000000..7226245bcc --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/cache.go @@ -0,0 +1,719 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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. + */ + +// Ristretto is a fast, fixed size, in-memory cache with a dual focus on +// throughput and hit ratio performance. You can easily add Ristretto to an +// existing system and keep the most valuable data where you need it. +package ristretto + +import ( + "bytes" + "errors" + "fmt" + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/dgraph-io/ristretto/z" +) + +var ( + // TODO: find the optimal value for this or make it configurable + setBufSize = 32 * 1024 +) + +type itemCallback func(*Item) + +const itemSize = int64(unsafe.Sizeof(storeItem{})) + +// Cache is a thread-safe implementation of a hashmap with a TinyLFU admission +// policy and a Sampled LFU eviction policy. You can use the same Cache instance +// from as many goroutines as you want. +type Cache struct { + // store is the central concurrent hashmap where key-value items are stored. + store store + // policy determines what gets let in to the cache and what gets kicked out. + policy policy + // getBuf is a custom ring buffer implementation that gets pushed to when + // keys are read. + getBuf *ringBuffer + // setBuf is a buffer allowing us to batch/drop Sets during times of high + // contention. + setBuf chan *Item + // onEvict is called for item evictions. + onEvict itemCallback + // onReject is called when an item is rejected via admission policy. + onReject itemCallback + // onExit is called whenever a value goes out of scope from the cache. + onExit (func(interface{})) + // KeyToHash function is used to customize the key hashing algorithm. + // Each key will be hashed using the provided function. If keyToHash value + // is not set, the default keyToHash function is used. + keyToHash func(interface{}) (uint64, uint64) + // stop is used to stop the processItems goroutine. + stop chan struct{} + // indicates whether cache is closed. + isClosed bool + // cost calculates cost from a value. + cost func(value interface{}) int64 + // ignoreInternalCost dictates whether to ignore the cost of internally storing + // the item in the cost calculation. + ignoreInternalCost bool + // cleanupTicker is used to periodically check for entries whose TTL has passed. + cleanupTicker *time.Ticker + // Metrics contains a running log of important statistics like hits, misses, + // and dropped items. + Metrics *Metrics +} + +// Config is passed to NewCache for creating new Cache instances. +type Config struct { + // NumCounters determines the number of counters (keys) to keep that hold + // access frequency information. It's generally a good idea to have more + // counters than the max cache capacity, as this will improve eviction + // accuracy and subsequent hit ratios. + // + // For example, if you expect your cache to hold 1,000,000 items when full, + // NumCounters should be 10,000,000 (10x). Each counter takes up roughly + // 3 bytes (4 bits for each counter * 4 copies plus about a byte per + // counter for the bloom filter). Note that the number of counters is + // internally rounded up to the nearest power of 2, so the space usage + // may be a little larger than 3 bytes * NumCounters. + NumCounters int64 + // MaxCost can be considered as the cache capacity, in whatever units you + // choose to use. + // + // For example, if you want the cache to have a max capacity of 100MB, you + // would set MaxCost to 100,000,000 and pass an item's number of bytes as + // the `cost` parameter for calls to Set. If new items are accepted, the + // eviction process will take care of making room for the new item and not + // overflowing the MaxCost value. + MaxCost int64 + // BufferItems determines the size of Get buffers. + // + // Unless you have a rare use case, using `64` as the BufferItems value + // results in good performance. + BufferItems int64 + // Metrics determines whether cache statistics are kept during the cache's + // lifetime. There *is* some overhead to keeping statistics, so you should + // only set this flag to true when testing or throughput performance isn't a + // major factor. + Metrics bool + // OnEvict is called for every eviction and passes the hashed key, value, + // and cost to the function. + OnEvict func(item *Item) + // OnReject is called for every rejection done via the policy. + OnReject func(item *Item) + // OnExit is called whenever a value is removed from cache. This can be + // used to do manual memory deallocation. Would also be called on eviction + // and rejection of the value. + OnExit func(val interface{}) + // KeyToHash function is used to customize the key hashing algorithm. + // Each key will be hashed using the provided function. If keyToHash value + // is not set, the default keyToHash function is used. + KeyToHash func(key interface{}) (uint64, uint64) + // Cost evaluates a value and outputs a corresponding cost. This function + // is ran after Set is called for a new item or an item update with a cost + // param of 0. + Cost func(value interface{}) int64 + // IgnoreInternalCost set to true indicates to the cache that the cost of + // internally storing the value should be ignored. This is useful when the + // cost passed to set is not using bytes as units. Keep in mind that setting + // this to true will increase the memory usage. + IgnoreInternalCost bool +} + +type itemFlag byte + +const ( + itemNew itemFlag = iota + itemDelete + itemUpdate +) + +// Item is passed to setBuf so items can eventually be added to the cache. +type Item struct { + flag itemFlag + Key uint64 + Conflict uint64 + Value interface{} + Cost int64 + Expiration time.Time + wg *sync.WaitGroup +} + +// NewCache returns a new Cache instance and any configuration errors, if any. +func NewCache(config *Config) (*Cache, error) { + switch { + case config.NumCounters == 0: + return nil, errors.New("NumCounters can't be zero") + case config.MaxCost == 0: + return nil, errors.New("MaxCost can't be zero") + case config.BufferItems == 0: + return nil, errors.New("BufferItems can't be zero") + } + policy := newPolicy(config.NumCounters, config.MaxCost) + cache := &Cache{ + store: newStore(), + policy: policy, + getBuf: newRingBuffer(policy, config.BufferItems), + setBuf: make(chan *Item, setBufSize), + keyToHash: config.KeyToHash, + stop: make(chan struct{}), + cost: config.Cost, + ignoreInternalCost: config.IgnoreInternalCost, + cleanupTicker: time.NewTicker(time.Duration(bucketDurationSecs) * time.Second / 2), + } + cache.onExit = func(val interface{}) { + if config.OnExit != nil && val != nil { + config.OnExit(val) + } + } + cache.onEvict = func(item *Item) { + if config.OnEvict != nil { + config.OnEvict(item) + } + cache.onExit(item.Value) + } + cache.onReject = func(item *Item) { + if config.OnReject != nil { + config.OnReject(item) + } + cache.onExit(item.Value) + } + if cache.keyToHash == nil { + cache.keyToHash = z.KeyToHash + } + if config.Metrics { + cache.collectMetrics() + } + // NOTE: benchmarks seem to show that performance decreases the more + // goroutines we have running cache.processItems(), so 1 should + // usually be sufficient + go cache.processItems() + return cache, nil +} + +func (c *Cache) Wait() { + if c == nil || c.isClosed { + return + } + wg := &sync.WaitGroup{} + wg.Add(1) + c.setBuf <- &Item{wg: wg} + wg.Wait() +} + +// Get returns the value (if any) and a boolean representing whether the +// value was found or not. The value can be nil and the boolean can be true at +// the same time. +func (c *Cache) Get(key interface{}) (interface{}, bool) { + if c == nil || c.isClosed || key == nil { + return nil, false + } + keyHash, conflictHash := c.keyToHash(key) + c.getBuf.Push(keyHash) + value, ok := c.store.Get(keyHash, conflictHash) + if ok { + c.Metrics.add(hit, keyHash, 1) + } else { + c.Metrics.add(miss, keyHash, 1) + } + return value, ok +} + +// Set attempts to add the key-value item to the cache. If it returns false, +// then the Set was dropped and the key-value item isn't added to the cache. If +// it returns true, there's still a chance it could be dropped by the policy if +// its determined that the key-value item isn't worth keeping, but otherwise the +// item will be added and other items will be evicted in order to make room. +// +// To dynamically evaluate the items cost using the Config.Coster function, set +// the cost parameter to 0 and Coster will be ran when needed in order to find +// the items true cost. +func (c *Cache) Set(key, value interface{}, cost int64) bool { + return c.SetWithTTL(key, value, cost, 0*time.Second) +} + +// SetWithTTL works like Set but adds a key-value pair to the cache that will expire +// after the specified TTL (time to live) has passed. A zero value means the value never +// expires, which is identical to calling Set. A negative value is a no-op and the value +// is discarded. +func (c *Cache) SetWithTTL(key, value interface{}, cost int64, ttl time.Duration) bool { + if c == nil || c.isClosed || key == nil { + return false + } + + var expiration time.Time + switch { + case ttl == 0: + // No expiration. + break + case ttl < 0: + // Treat this a a no-op. + return false + default: + expiration = time.Now().Add(ttl) + } + + keyHash, conflictHash := c.keyToHash(key) + i := &Item{ + flag: itemNew, + Key: keyHash, + Conflict: conflictHash, + Value: value, + Cost: cost, + Expiration: expiration, + } + // cost is eventually updated. The expiration must also be immediately updated + // to prevent items from being prematurely removed from the map. + if prev, ok := c.store.Update(i); ok { + c.onExit(prev) + i.flag = itemUpdate + } + // Attempt to send item to policy. + select { + case c.setBuf <- i: + return true + default: + if i.flag == itemUpdate { + // Return true if this was an update operation since we've already + // updated the store. For all the other operations (set/delete), we + // return false which means the item was not inserted. + return true + } + c.Metrics.add(dropSets, keyHash, 1) + return false + } +} + +// Del deletes the key-value item from the cache if it exists. +func (c *Cache) Del(key interface{}) { + if c == nil || c.isClosed || key == nil { + return + } + keyHash, conflictHash := c.keyToHash(key) + // Delete immediately. + _, prev := c.store.Del(keyHash, conflictHash) + c.onExit(prev) + // If we've set an item, it would be applied slightly later. + // So we must push the same item to `setBuf` with the deletion flag. + // This ensures that if a set is followed by a delete, it will be + // applied in the correct order. + c.setBuf <- &Item{ + flag: itemDelete, + Key: keyHash, + Conflict: conflictHash, + } +} + +// GetTTL returns the TTL for the specified key and a bool that is true if the +// item was found and is not expired. +func (c *Cache) GetTTL(key interface{}) (time.Duration, bool) { + if c == nil || key == nil { + return 0, false + } + + keyHash, conflictHash := c.keyToHash(key) + if _, ok := c.store.Get(keyHash, conflictHash); !ok { + // not found + return 0, false + } + + expiration := c.store.Expiration(keyHash) + if expiration.IsZero() { + // found but no expiration + return 0, true + } + + if time.Now().After(expiration) { + // found but expired + return 0, false + } + + return time.Until(expiration), true +} + +// Close stops all goroutines and closes all channels. +func (c *Cache) Close() { + if c == nil || c.isClosed { + return + } + c.Clear() + + // Block until processItems goroutine is returned. + c.stop <- struct{}{} + close(c.stop) + close(c.setBuf) + c.policy.Close() + c.isClosed = true +} + +// Clear empties the hashmap and zeroes all policy counters. Note that this is +// not an atomic operation (but that shouldn't be a problem as it's assumed that +// Set/Get calls won't be occurring until after this). +func (c *Cache) Clear() { + if c == nil || c.isClosed { + return + } + // Block until processItems goroutine is returned. + c.stop <- struct{}{} + + // Clear out the setBuf channel. +loop: + for { + select { + case i := <-c.setBuf: + if i.wg != nil { + i.wg.Done() + continue + } + if i.flag != itemUpdate { + // In itemUpdate, the value is already set in the store. So, no need to call + // onEvict here. + c.onEvict(i) + } + default: + break loop + } + } + + // Clear value hashmap and policy data. + c.policy.Clear() + c.store.Clear(c.onEvict) + // Only reset metrics if they're enabled. + if c.Metrics != nil { + c.Metrics.Clear() + } + // Restart processItems goroutine. + go c.processItems() +} + +// MaxCost returns the max cost of the cache. +func (c *Cache) MaxCost() int64 { + if c == nil { + return 0 + } + return c.policy.MaxCost() +} + +// UpdateMaxCost updates the maxCost of an existing cache. +func (c *Cache) UpdateMaxCost(maxCost int64) { + if c == nil { + return + } + c.policy.UpdateMaxCost(maxCost) +} + +// processItems is ran by goroutines processing the Set buffer. +func (c *Cache) processItems() { + startTs := make(map[uint64]time.Time) + numToKeep := 100000 // TODO: Make this configurable via options. + + trackAdmission := func(key uint64) { + if c.Metrics == nil { + return + } + startTs[key] = time.Now() + if len(startTs) > numToKeep { + for k := range startTs { + if len(startTs) <= numToKeep { + break + } + delete(startTs, k) + } + } + } + onEvict := func(i *Item) { + if ts, has := startTs[i.Key]; has { + c.Metrics.trackEviction(int64(time.Since(ts) / time.Second)) + delete(startTs, i.Key) + } + if c.onEvict != nil { + c.onEvict(i) + } + } + + for { + select { + case i := <-c.setBuf: + if i.wg != nil { + i.wg.Done() + continue + } + // Calculate item cost value if new or update. + if i.Cost == 0 && c.cost != nil && i.flag != itemDelete { + i.Cost = c.cost(i.Value) + } + if !c.ignoreInternalCost { + // Add the cost of internally storing the object. + i.Cost += itemSize + } + + switch i.flag { + case itemNew: + victims, added := c.policy.Add(i.Key, i.Cost) + if added { + c.store.Set(i) + c.Metrics.add(keyAdd, i.Key, 1) + trackAdmission(i.Key) + } else { + c.onReject(i) + } + for _, victim := range victims { + victim.Conflict, victim.Value = c.store.Del(victim.Key, 0) + onEvict(victim) + } + + case itemUpdate: + c.policy.Update(i.Key, i.Cost) + + case itemDelete: + c.policy.Del(i.Key) // Deals with metrics updates. + _, val := c.store.Del(i.Key, i.Conflict) + c.onExit(val) + } + case <-c.cleanupTicker.C: + c.store.Cleanup(c.policy, onEvict) + case <-c.stop: + return + } + } +} + +// collectMetrics just creates a new *Metrics instance and adds the pointers +// to the cache and policy instances. +func (c *Cache) collectMetrics() { + c.Metrics = newMetrics() + c.policy.CollectMetrics(c.Metrics) +} + +type metricType int + +const ( + // The following 2 keep track of hits and misses. + hit = iota + miss + // The following 3 keep track of number of keys added, updated and evicted. + keyAdd + keyUpdate + keyEvict + // The following 2 keep track of cost of keys added and evicted. + costAdd + costEvict + // The following keep track of how many sets were dropped or rejected later. + dropSets + rejectSets + // The following 2 keep track of how many gets were kept and dropped on the + // floor. + dropGets + keepGets + // This should be the final enum. Other enums should be set before this. + doNotUse +) + +func stringFor(t metricType) string { + switch t { + case hit: + return "hit" + case miss: + return "miss" + case keyAdd: + return "keys-added" + case keyUpdate: + return "keys-updated" + case keyEvict: + return "keys-evicted" + case costAdd: + return "cost-added" + case costEvict: + return "cost-evicted" + case dropSets: + return "sets-dropped" + case rejectSets: + return "sets-rejected" // by policy. + case dropGets: + return "gets-dropped" + case keepGets: + return "gets-kept" + default: + return "unidentified" + } +} + +// Metrics is a snapshot of performance statistics for the lifetime of a cache instance. +type Metrics struct { + all [doNotUse][]*uint64 + + mu sync.RWMutex + life *z.HistogramData // Tracks the life expectancy of a key. +} + +func newMetrics() *Metrics { + s := &Metrics{ + life: z.NewHistogramData(z.HistogramBounds(1, 16)), + } + for i := 0; i < doNotUse; i++ { + s.all[i] = make([]*uint64, 256) + slice := s.all[i] + for j := range slice { + slice[j] = new(uint64) + } + } + return s +} + +func (p *Metrics) add(t metricType, hash, delta uint64) { + if p == nil { + return + } + valp := p.all[t] + // Avoid false sharing by padding at least 64 bytes of space between two + // atomic counters which would be incremented. + idx := (hash % 25) * 10 + atomic.AddUint64(valp[idx], delta) +} + +func (p *Metrics) get(t metricType) uint64 { + if p == nil { + return 0 + } + valp := p.all[t] + var total uint64 + for i := range valp { + total += atomic.LoadUint64(valp[i]) + } + return total +} + +// Hits is the number of Get calls where a value was found for the corresponding key. +func (p *Metrics) Hits() uint64 { + return p.get(hit) +} + +// Misses is the number of Get calls where a value was not found for the corresponding key. +func (p *Metrics) Misses() uint64 { + return p.get(miss) +} + +// KeysAdded is the total number of Set calls where a new key-value item was added. +func (p *Metrics) KeysAdded() uint64 { + return p.get(keyAdd) +} + +// KeysUpdated is the total number of Set calls where the value was updated. +func (p *Metrics) KeysUpdated() uint64 { + return p.get(keyUpdate) +} + +// KeysEvicted is the total number of keys evicted. +func (p *Metrics) KeysEvicted() uint64 { + return p.get(keyEvict) +} + +// CostAdded is the sum of costs that have been added (successful Set calls). +func (p *Metrics) CostAdded() uint64 { + return p.get(costAdd) +} + +// CostEvicted is the sum of all costs that have been evicted. +func (p *Metrics) CostEvicted() uint64 { + return p.get(costEvict) +} + +// SetsDropped is the number of Set calls that don't make it into internal +// buffers (due to contention or some other reason). +func (p *Metrics) SetsDropped() uint64 { + return p.get(dropSets) +} + +// SetsRejected is the number of Set calls rejected by the policy (TinyLFU). +func (p *Metrics) SetsRejected() uint64 { + return p.get(rejectSets) +} + +// GetsDropped is the number of Get counter increments that are dropped +// internally. +func (p *Metrics) GetsDropped() uint64 { + return p.get(dropGets) +} + +// GetsKept is the number of Get counter increments that are kept. +func (p *Metrics) GetsKept() uint64 { + return p.get(keepGets) +} + +// Ratio is the number of Hits over all accesses (Hits + Misses). This is the +// percentage of successful Get calls. +func (p *Metrics) Ratio() float64 { + if p == nil { + return 0.0 + } + hits, misses := p.get(hit), p.get(miss) + if hits == 0 && misses == 0 { + return 0.0 + } + return float64(hits) / float64(hits+misses) +} + +func (p *Metrics) trackEviction(numSeconds int64) { + if p == nil { + return + } + p.mu.Lock() + defer p.mu.Unlock() + p.life.Update(numSeconds) +} + +func (p *Metrics) LifeExpectancySeconds() *z.HistogramData { + if p == nil { + return nil + } + p.mu.RLock() + defer p.mu.RUnlock() + return p.life.Copy() +} + +// Clear resets all the metrics. +func (p *Metrics) Clear() { + if p == nil { + return + } + for i := 0; i < doNotUse; i++ { + for j := range p.all[i] { + atomic.StoreUint64(p.all[i][j], 0) + } + } + p.mu.Lock() + p.life = z.NewHistogramData(z.HistogramBounds(1, 16)) + p.mu.Unlock() +} + +// String returns a string representation of the metrics. +func (p *Metrics) String() string { + if p == nil { + return "" + } + var buf bytes.Buffer + for i := 0; i < doNotUse; i++ { + t := metricType(i) + fmt.Fprintf(&buf, "%s: %d ", stringFor(t), p.get(t)) + } + fmt.Fprintf(&buf, "gets-total: %d ", p.get(hit)+p.get(miss)) + fmt.Fprintf(&buf, "hit-ratio: %.2f", p.Ratio()) + return buf.String() +} diff --git a/vendor/github.com/dgraph-io/ristretto/policy.go b/vendor/github.com/dgraph-io/ristretto/policy.go new file mode 100644 index 0000000000..bf23f91fd9 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/policy.go @@ -0,0 +1,423 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 ristretto + +import ( + "math" + "sync" + "sync/atomic" + + "github.com/dgraph-io/ristretto/z" +) + +const ( + // lfuSample is the number of items to sample when looking at eviction + // candidates. 5 seems to be the most optimal number [citation needed]. + lfuSample = 5 +) + +// policy is the interface encapsulating eviction/admission behavior. +// +// TODO: remove this interface and just rename defaultPolicy to policy, as we +// are probably only going to use/implement/maintain one policy. +type policy interface { + ringConsumer + // Add attempts to Add the key-cost pair to the Policy. It returns a slice + // of evicted keys and a bool denoting whether or not the key-cost pair + // was added. If it returns true, the key should be stored in cache. + Add(uint64, int64) ([]*Item, bool) + // Has returns true if the key exists in the Policy. + Has(uint64) bool + // Del deletes the key from the Policy. + Del(uint64) + // Cap returns the available capacity. + Cap() int64 + // Close stops all goroutines and closes all channels. + Close() + // Update updates the cost value for the key. + Update(uint64, int64) + // Cost returns the cost value of a key or -1 if missing. + Cost(uint64) int64 + // Optionally, set stats object to track how policy is performing. + CollectMetrics(*Metrics) + // Clear zeroes out all counters and clears hashmaps. + Clear() + // MaxCost returns the current max cost of the cache policy. + MaxCost() int64 + // UpdateMaxCost updates the max cost of the cache policy. + UpdateMaxCost(int64) +} + +func newPolicy(numCounters, maxCost int64) policy { + return newDefaultPolicy(numCounters, maxCost) +} + +type defaultPolicy struct { + sync.Mutex + admit *tinyLFU + evict *sampledLFU + itemsCh chan []uint64 + stop chan struct{} + isClosed bool + metrics *Metrics +} + +func newDefaultPolicy(numCounters, maxCost int64) *defaultPolicy { + p := &defaultPolicy{ + admit: newTinyLFU(numCounters), + evict: newSampledLFU(maxCost), + itemsCh: make(chan []uint64, 3), + stop: make(chan struct{}), + } + go p.processItems() + return p +} + +func (p *defaultPolicy) CollectMetrics(metrics *Metrics) { + p.metrics = metrics + p.evict.metrics = metrics +} + +type policyPair struct { + key uint64 + cost int64 +} + +func (p *defaultPolicy) processItems() { + for { + select { + case items := <-p.itemsCh: + p.Lock() + p.admit.Push(items) + p.Unlock() + case <-p.stop: + return + } + } +} + +func (p *defaultPolicy) Push(keys []uint64) bool { + if p.isClosed { + return false + } + + if len(keys) == 0 { + return true + } + + select { + case p.itemsCh <- keys: + p.metrics.add(keepGets, keys[0], uint64(len(keys))) + return true + default: + p.metrics.add(dropGets, keys[0], uint64(len(keys))) + return false + } +} + +// Add decides whether the item with the given key and cost should be accepted by +// the policy. It returns the list of victims that have been evicted and a boolean +// indicating whether the incoming item should be accepted. +func (p *defaultPolicy) Add(key uint64, cost int64) ([]*Item, bool) { + p.Lock() + defer p.Unlock() + + // Cannot add an item bigger than entire cache. + if cost > p.evict.getMaxCost() { + return nil, false + } + + // No need to go any further if the item is already in the cache. + if has := p.evict.updateIfHas(key, cost); has { + // An update does not count as an addition, so return false. + return nil, false + } + + // If the execution reaches this point, the key doesn't exist in the cache. + // Calculate the remaining room in the cache (usually bytes). + room := p.evict.roomLeft(cost) + if room >= 0 { + // There's enough room in the cache to store the new item without + // overflowing. Do that now and stop here. + p.evict.add(key, cost) + p.metrics.add(costAdd, key, uint64(cost)) + return nil, true + } + + // incHits is the hit count for the incoming item. + incHits := p.admit.Estimate(key) + // sample is the eviction candidate pool to be filled via random sampling. + // TODO: perhaps we should use a min heap here. Right now our time + // complexity is N for finding the min. Min heap should bring it down to + // O(lg N). + sample := make([]*policyPair, 0, lfuSample) + // As items are evicted they will be appended to victims. + victims := make([]*Item, 0) + + // Delete victims until there's enough space or a minKey is found that has + // more hits than incoming item. + for ; room < 0; room = p.evict.roomLeft(cost) { + // Fill up empty slots in sample. + sample = p.evict.fillSample(sample) + + // Find minimally used item in sample. + minKey, minHits, minId, minCost := uint64(0), int64(math.MaxInt64), 0, int64(0) + for i, pair := range sample { + // Look up hit count for sample key. + if hits := p.admit.Estimate(pair.key); hits < minHits { + minKey, minHits, minId, minCost = pair.key, hits, i, pair.cost + } + } + + // If the incoming item isn't worth keeping in the policy, reject. + if incHits < minHits { + p.metrics.add(rejectSets, key, 1) + return victims, false + } + + // Delete the victim from metadata. + p.evict.del(minKey) + + // Delete the victim from sample. + sample[minId] = sample[len(sample)-1] + sample = sample[:len(sample)-1] + // Store victim in evicted victims slice. + victims = append(victims, &Item{ + Key: minKey, + Conflict: 0, + Cost: minCost, + }) + } + + p.evict.add(key, cost) + p.metrics.add(costAdd, key, uint64(cost)) + return victims, true +} + +func (p *defaultPolicy) Has(key uint64) bool { + p.Lock() + _, exists := p.evict.keyCosts[key] + p.Unlock() + return exists +} + +func (p *defaultPolicy) Del(key uint64) { + p.Lock() + p.evict.del(key) + p.Unlock() +} + +func (p *defaultPolicy) Cap() int64 { + p.Lock() + capacity := int64(p.evict.getMaxCost() - p.evict.used) + p.Unlock() + return capacity +} + +func (p *defaultPolicy) Update(key uint64, cost int64) { + p.Lock() + p.evict.updateIfHas(key, cost) + p.Unlock() +} + +func (p *defaultPolicy) Cost(key uint64) int64 { + p.Lock() + if cost, found := p.evict.keyCosts[key]; found { + p.Unlock() + return cost + } + p.Unlock() + return -1 +} + +func (p *defaultPolicy) Clear() { + p.Lock() + p.admit.clear() + p.evict.clear() + p.Unlock() +} + +func (p *defaultPolicy) Close() { + if p.isClosed { + return + } + + // Block until the p.processItems goroutine returns. + p.stop <- struct{}{} + close(p.stop) + close(p.itemsCh) + p.isClosed = true +} + +func (p *defaultPolicy) MaxCost() int64 { + if p == nil || p.evict == nil { + return 0 + } + return p.evict.getMaxCost() +} + +func (p *defaultPolicy) UpdateMaxCost(maxCost int64) { + if p == nil || p.evict == nil { + return + } + p.evict.updateMaxCost(maxCost) +} + +// sampledLFU is an eviction helper storing key-cost pairs. +type sampledLFU struct { + // NOTE: align maxCost to 64-bit boundary for use with atomic. + // As per https://golang.org/pkg/sync/atomic/: "On ARM, x86-32, + // and 32-bit MIPS, it is the caller’s responsibility to arrange + // for 64-bit alignment of 64-bit words accessed atomically. + // The first word in a variable or in an allocated struct, array, + // or slice can be relied upon to be 64-bit aligned." + maxCost int64 + used int64 + metrics *Metrics + keyCosts map[uint64]int64 +} + +func newSampledLFU(maxCost int64) *sampledLFU { + return &sampledLFU{ + keyCosts: make(map[uint64]int64), + maxCost: maxCost, + } +} + +func (p *sampledLFU) getMaxCost() int64 { + return atomic.LoadInt64(&p.maxCost) +} + +func (p *sampledLFU) updateMaxCost(maxCost int64) { + atomic.StoreInt64(&p.maxCost, maxCost) +} + +func (p *sampledLFU) roomLeft(cost int64) int64 { + return p.getMaxCost() - (p.used + cost) +} + +func (p *sampledLFU) fillSample(in []*policyPair) []*policyPair { + if len(in) >= lfuSample { + return in + } + for key, cost := range p.keyCosts { + in = append(in, &policyPair{key, cost}) + if len(in) >= lfuSample { + return in + } + } + return in +} + +func (p *sampledLFU) del(key uint64) { + cost, ok := p.keyCosts[key] + if !ok { + return + } + p.used -= cost + delete(p.keyCosts, key) + p.metrics.add(costEvict, key, uint64(cost)) + p.metrics.add(keyEvict, key, 1) +} + +func (p *sampledLFU) add(key uint64, cost int64) { + p.keyCosts[key] = cost + p.used += cost +} + +func (p *sampledLFU) updateIfHas(key uint64, cost int64) bool { + if prev, found := p.keyCosts[key]; found { + // Update the cost of an existing key, but don't worry about evicting. + // Evictions will be handled the next time a new item is added. + p.metrics.add(keyUpdate, key, 1) + if prev > cost { + diff := prev - cost + p.metrics.add(costAdd, key, ^uint64(uint64(diff)-1)) + } else if cost > prev { + diff := cost - prev + p.metrics.add(costAdd, key, uint64(diff)) + } + p.used += cost - prev + p.keyCosts[key] = cost + return true + } + return false +} + +func (p *sampledLFU) clear() { + p.used = 0 + p.keyCosts = make(map[uint64]int64) +} + +// tinyLFU is an admission helper that keeps track of access frequency using +// tiny (4-bit) counters in the form of a count-min sketch. +// tinyLFU is NOT thread safe. +type tinyLFU struct { + freq *cmSketch + door *z.Bloom + incrs int64 + resetAt int64 +} + +func newTinyLFU(numCounters int64) *tinyLFU { + return &tinyLFU{ + freq: newCmSketch(numCounters), + door: z.NewBloomFilter(float64(numCounters), 0.01), + resetAt: numCounters, + } +} + +func (p *tinyLFU) Push(keys []uint64) { + for _, key := range keys { + p.Increment(key) + } +} + +func (p *tinyLFU) Estimate(key uint64) int64 { + hits := p.freq.Estimate(key) + if p.door.Has(key) { + hits++ + } + return hits +} + +func (p *tinyLFU) Increment(key uint64) { + // Flip doorkeeper bit if not already done. + if added := p.door.AddIfNotHas(key); !added { + // Increment count-min counter if doorkeeper bit is already set. + p.freq.Increment(key) + } + p.incrs++ + if p.incrs >= p.resetAt { + p.reset() + } +} + +func (p *tinyLFU) reset() { + // Zero out incrs. + p.incrs = 0 + // clears doorkeeper bits + p.door.Clear() + // halves count-min counters + p.freq.Reset() +} + +func (p *tinyLFU) clear() { + p.incrs = 0 + p.door.Clear() + p.freq.Clear() +} diff --git a/vendor/github.com/dgraph-io/ristretto/ring.go b/vendor/github.com/dgraph-io/ristretto/ring.go new file mode 100644 index 0000000000..5dbed4cc59 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/ring.go @@ -0,0 +1,91 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 ristretto + +import ( + "sync" +) + +// ringConsumer is the user-defined object responsible for receiving and +// processing items in batches when buffers are drained. +type ringConsumer interface { + Push([]uint64) bool +} + +// ringStripe is a singular ring buffer that is not concurrent safe. +type ringStripe struct { + cons ringConsumer + data []uint64 + capa int +} + +func newRingStripe(cons ringConsumer, capa int64) *ringStripe { + return &ringStripe{ + cons: cons, + data: make([]uint64, 0, capa), + capa: int(capa), + } +} + +// Push appends an item in the ring buffer and drains (copies items and +// sends to Consumer) if full. +func (s *ringStripe) Push(item uint64) { + s.data = append(s.data, item) + // Decide if the ring buffer should be drained. + if len(s.data) >= s.capa { + // Send elements to consumer and create a new ring stripe. + if s.cons.Push(s.data) { + s.data = make([]uint64, 0, s.capa) + } else { + s.data = s.data[:0] + } + } +} + +// ringBuffer stores multiple buffers (stripes) and distributes Pushed items +// between them to lower contention. +// +// This implements the "batching" process described in the BP-Wrapper paper +// (section III part A). +type ringBuffer struct { + pool *sync.Pool +} + +// newRingBuffer returns a striped ring buffer. The Consumer in ringConfig will +// be called when individual stripes are full and need to drain their elements. +func newRingBuffer(cons ringConsumer, capa int64) *ringBuffer { + // LOSSY buffers use a very simple sync.Pool for concurrently reusing + // stripes. We do lose some stripes due to GC (unheld items in sync.Pool + // are cleared), but the performance gains generally outweigh the small + // percentage of elements lost. The performance primarily comes from + // low-level runtime functions used in the standard library that aren't + // available to us (such as runtime_procPin()). + return &ringBuffer{ + pool: &sync.Pool{ + New: func() interface{} { return newRingStripe(cons, capa) }, + }, + } +} + +// Push adds an element to one of the internal stripes and possibly drains if +// the stripe becomes full. +func (b *ringBuffer) Push(item uint64) { + // Reuse or create a new stripe. + stripe := b.pool.Get().(*ringStripe) + stripe.Push(item) + b.pool.Put(stripe) +} diff --git a/vendor/github.com/dgraph-io/ristretto/sketch.go b/vendor/github.com/dgraph-io/ristretto/sketch.go new file mode 100644 index 0000000000..10f414689a --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/sketch.go @@ -0,0 +1,155 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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. + */ + +// This package includes multiple probabalistic data structures needed for +// admission/eviction metadata. Most are Counting Bloom Filter variations, but +// a caching-specific feature that is also required is a "freshness" mechanism, +// which basically serves as a "lifetime" process. This freshness mechanism +// was described in the original TinyLFU paper [1], but other mechanisms may +// be better suited for certain data distributions. +// +// [1]: https://arxiv.org/abs/1512.00727 +package ristretto + +import ( + "fmt" + "math/rand" + "time" +) + +// cmSketch is a Count-Min sketch implementation with 4-bit counters, heavily +// based on Damian Gryski's CM4 [1]. +// +// [1]: https://github.com/dgryski/go-tinylfu/blob/master/cm4.go +type cmSketch struct { + rows [cmDepth]cmRow + seed [cmDepth]uint64 + mask uint64 +} + +const ( + // cmDepth is the number of counter copies to store (think of it as rows). + cmDepth = 4 +) + +func newCmSketch(numCounters int64) *cmSketch { + if numCounters == 0 { + panic("cmSketch: bad numCounters") + } + // Get the next power of 2 for better cache performance. + numCounters = next2Power(numCounters) + sketch := &cmSketch{mask: uint64(numCounters - 1)} + // Initialize rows of counters and seeds. + source := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := 0; i < cmDepth; i++ { + sketch.seed[i] = source.Uint64() + sketch.rows[i] = newCmRow(numCounters) + } + return sketch +} + +// Increment increments the count(ers) for the specified key. +func (s *cmSketch) Increment(hashed uint64) { + for i := range s.rows { + s.rows[i].increment((hashed ^ s.seed[i]) & s.mask) + } +} + +// Estimate returns the value of the specified key. +func (s *cmSketch) Estimate(hashed uint64) int64 { + min := byte(255) + for i := range s.rows { + val := s.rows[i].get((hashed ^ s.seed[i]) & s.mask) + if val < min { + min = val + } + } + return int64(min) +} + +// Reset halves all counter values. +func (s *cmSketch) Reset() { + for _, r := range s.rows { + r.reset() + } +} + +// Clear zeroes all counters. +func (s *cmSketch) Clear() { + for _, r := range s.rows { + r.clear() + } +} + +// cmRow is a row of bytes, with each byte holding two counters. +type cmRow []byte + +func newCmRow(numCounters int64) cmRow { + return make(cmRow, numCounters/2) +} + +func (r cmRow) get(n uint64) byte { + return byte(r[n/2]>>((n&1)*4)) & 0x0f +} + +func (r cmRow) increment(n uint64) { + // Index of the counter. + i := n / 2 + // Shift distance (even 0, odd 4). + s := (n & 1) * 4 + // Counter value. + v := (r[i] >> s) & 0x0f + // Only increment if not max value (overflow wrap is bad for LFU). + if v < 15 { + r[i] += 1 << s + } +} + +func (r cmRow) reset() { + // Halve each counter. + for i := range r { + r[i] = (r[i] >> 1) & 0x77 + } +} + +func (r cmRow) clear() { + // Zero each counter. + for i := range r { + r[i] = 0 + } +} + +func (r cmRow) string() string { + s := "" + for i := uint64(0); i < uint64(len(r)*2); i++ { + s += fmt.Sprintf("%02d ", (r[(i/2)]>>((i&1)*4))&0x0f) + } + s = s[:len(s)-1] + return s +} + +// next2Power rounds x up to the next power of 2, if it's not already one. +func next2Power(x int64) int64 { + x-- + x |= x >> 1 + x |= x >> 2 + x |= x >> 4 + x |= x >> 8 + x |= x >> 16 + x |= x >> 32 + x++ + return x +} diff --git a/vendor/github.com/dgraph-io/ristretto/store.go b/vendor/github.com/dgraph-io/ristretto/store.go new file mode 100644 index 0000000000..e42a98b787 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/store.go @@ -0,0 +1,242 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 ristretto + +import ( + "sync" + "time" +) + +// TODO: Do we need this to be a separate struct from Item? +type storeItem struct { + key uint64 + conflict uint64 + value interface{} + expiration time.Time +} + +// store is the interface fulfilled by all hash map implementations in this +// file. Some hash map implementations are better suited for certain data +// distributions than others, so this allows us to abstract that out for use +// in Ristretto. +// +// Every store is safe for concurrent usage. +type store interface { + // Get returns the value associated with the key parameter. + Get(uint64, uint64) (interface{}, bool) + // Expiration returns the expiration time for this key. + Expiration(uint64) time.Time + // Set adds the key-value pair to the Map or updates the value if it's + // already present. The key-value pair is passed as a pointer to an + // item object. + Set(*Item) + // Del deletes the key-value pair from the Map. + Del(uint64, uint64) (uint64, interface{}) + // Update attempts to update the key with a new value and returns true if + // successful. + Update(*Item) (interface{}, bool) + // Cleanup removes items that have an expired TTL. + Cleanup(policy policy, onEvict itemCallback) + // Clear clears all contents of the store. + Clear(onEvict itemCallback) +} + +// newStore returns the default store implementation. +func newStore() store { + return newShardedMap() +} + +const numShards uint64 = 256 + +type shardedMap struct { + shards []*lockedMap + expiryMap *expirationMap +} + +func newShardedMap() *shardedMap { + sm := &shardedMap{ + shards: make([]*lockedMap, int(numShards)), + expiryMap: newExpirationMap(), + } + for i := range sm.shards { + sm.shards[i] = newLockedMap(sm.expiryMap) + } + return sm +} + +func (sm *shardedMap) Get(key, conflict uint64) (interface{}, bool) { + return sm.shards[key%numShards].get(key, conflict) +} + +func (sm *shardedMap) Expiration(key uint64) time.Time { + return sm.shards[key%numShards].Expiration(key) +} + +func (sm *shardedMap) Set(i *Item) { + if i == nil { + // If item is nil make this Set a no-op. + return + } + + sm.shards[i.Key%numShards].Set(i) +} + +func (sm *shardedMap) Del(key, conflict uint64) (uint64, interface{}) { + return sm.shards[key%numShards].Del(key, conflict) +} + +func (sm *shardedMap) Update(newItem *Item) (interface{}, bool) { + return sm.shards[newItem.Key%numShards].Update(newItem) +} + +func (sm *shardedMap) Cleanup(policy policy, onEvict itemCallback) { + sm.expiryMap.cleanup(sm, policy, onEvict) +} + +func (sm *shardedMap) Clear(onEvict itemCallback) { + for i := uint64(0); i < numShards; i++ { + sm.shards[i].Clear(onEvict) + } +} + +type lockedMap struct { + sync.RWMutex + data map[uint64]storeItem + em *expirationMap +} + +func newLockedMap(em *expirationMap) *lockedMap { + return &lockedMap{ + data: make(map[uint64]storeItem), + em: em, + } +} + +func (m *lockedMap) get(key, conflict uint64) (interface{}, bool) { + m.RLock() + item, ok := m.data[key] + m.RUnlock() + if !ok { + return nil, false + } + if conflict != 0 && (conflict != item.conflict) { + return nil, false + } + + // Handle expired items. + if !item.expiration.IsZero() && time.Now().After(item.expiration) { + return nil, false + } + return item.value, true +} + +func (m *lockedMap) Expiration(key uint64) time.Time { + m.RLock() + defer m.RUnlock() + return m.data[key].expiration +} + +func (m *lockedMap) Set(i *Item) { + if i == nil { + // If the item is nil make this Set a no-op. + return + } + + m.Lock() + defer m.Unlock() + item, ok := m.data[i.Key] + + if ok { + // The item existed already. We need to check the conflict key and reject the + // update if they do not match. Only after that the expiration map is updated. + if i.Conflict != 0 && (i.Conflict != item.conflict) { + return + } + m.em.update(i.Key, i.Conflict, item.expiration, i.Expiration) + } else { + // The value is not in the map already. There's no need to return anything. + // Simply add the expiration map. + m.em.add(i.Key, i.Conflict, i.Expiration) + } + + m.data[i.Key] = storeItem{ + key: i.Key, + conflict: i.Conflict, + value: i.Value, + expiration: i.Expiration, + } +} + +func (m *lockedMap) Del(key, conflict uint64) (uint64, interface{}) { + m.Lock() + item, ok := m.data[key] + if !ok { + m.Unlock() + return 0, nil + } + if conflict != 0 && (conflict != item.conflict) { + m.Unlock() + return 0, nil + } + + if !item.expiration.IsZero() { + m.em.del(key, item.expiration) + } + + delete(m.data, key) + m.Unlock() + return item.conflict, item.value +} + +func (m *lockedMap) Update(newItem *Item) (interface{}, bool) { + m.Lock() + item, ok := m.data[newItem.Key] + if !ok { + m.Unlock() + return nil, false + } + if newItem.Conflict != 0 && (newItem.Conflict != item.conflict) { + m.Unlock() + return nil, false + } + + m.em.update(newItem.Key, newItem.Conflict, item.expiration, newItem.Expiration) + m.data[newItem.Key] = storeItem{ + key: newItem.Key, + conflict: newItem.Conflict, + value: newItem.Value, + expiration: newItem.Expiration, + } + + m.Unlock() + return item.value, true +} + +func (m *lockedMap) Clear(onEvict itemCallback) { + m.Lock() + i := &Item{} + if onEvict != nil { + for _, si := range m.data { + i.Key = si.key + i.Conflict = si.conflict + i.Value = si.value + onEvict(i) + } + } + m.data = make(map[uint64]storeItem) + m.Unlock() +} diff --git a/vendor/github.com/dgraph-io/ristretto/test.sh b/vendor/github.com/dgraph-io/ristretto/test.sh new file mode 100644 index 0000000000..d53b32d42a --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/test.sh @@ -0,0 +1,20 @@ +#! /bin/sh + +starttest() { + set -e + GO111MODULE=on go test -race ./... +} + +if [ -z "${TEAMCITY_VERSION}" ]; then + # running locally, so start test in a container + # TEAMCITY_VERSION=local will avoid recursive calls, when it would be running in container + docker run --rm --name ristretto-test -ti \ + -v `pwd`:/go/src/github.com/dgraph-io/ristretto \ + --workdir /go/src/github.com/dgraph-io/ristretto \ + --env TEAMCITY_VERSION=local \ + golang:1.13 \ + sh test.sh +else + # running in teamcity, since teamcity itself run this in container, let's simply run this + starttest +fi diff --git a/vendor/github.com/dgraph-io/ristretto/ttl.go b/vendor/github.com/dgraph-io/ristretto/ttl.go new file mode 100644 index 0000000000..337976ad43 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/ttl.go @@ -0,0 +1,147 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 ristretto + +import ( + "sync" + "time" +) + +var ( + // TODO: find the optimal value or make it configurable. + bucketDurationSecs = int64(5) +) + +func storageBucket(t time.Time) int64 { + return (t.Unix() / bucketDurationSecs) + 1 +} + +func cleanupBucket(t time.Time) int64 { + // The bucket to cleanup is always behind the storage bucket by one so that + // no elements in that bucket (which might not have expired yet) are deleted. + return storageBucket(t) - 1 +} + +// bucket type is a map of key to conflict. +type bucket map[uint64]uint64 + +// expirationMap is a map of bucket number to the corresponding bucket. +type expirationMap struct { + sync.RWMutex + buckets map[int64]bucket +} + +func newExpirationMap() *expirationMap { + return &expirationMap{ + buckets: make(map[int64]bucket), + } +} + +func (m *expirationMap) add(key, conflict uint64, expiration time.Time) { + if m == nil { + return + } + + // Items that don't expire don't need to be in the expiration map. + if expiration.IsZero() { + return + } + + bucketNum := storageBucket(expiration) + m.Lock() + defer m.Unlock() + + b, ok := m.buckets[bucketNum] + if !ok { + b = make(bucket) + m.buckets[bucketNum] = b + } + b[key] = conflict +} + +func (m *expirationMap) update(key, conflict uint64, oldExpTime, newExpTime time.Time) { + if m == nil { + return + } + + m.Lock() + defer m.Unlock() + + oldBucketNum := storageBucket(oldExpTime) + oldBucket, ok := m.buckets[oldBucketNum] + if ok { + delete(oldBucket, key) + } + + newBucketNum := storageBucket(newExpTime) + newBucket, ok := m.buckets[newBucketNum] + if !ok { + newBucket = make(bucket) + m.buckets[newBucketNum] = newBucket + } + newBucket[key] = conflict +} + +func (m *expirationMap) del(key uint64, expiration time.Time) { + if m == nil { + return + } + + bucketNum := storageBucket(expiration) + m.Lock() + defer m.Unlock() + _, ok := m.buckets[bucketNum] + if !ok { + return + } + delete(m.buckets[bucketNum], key) +} + +// cleanup removes all the items in the bucket that was just completed. It deletes +// those items from the store, and calls the onEvict function on those items. +// This function is meant to be called periodically. +func (m *expirationMap) cleanup(store store, policy policy, onEvict itemCallback) { + if m == nil { + return + } + + m.Lock() + now := time.Now() + bucketNum := cleanupBucket(now) + keys := m.buckets[bucketNum] + delete(m.buckets, bucketNum) + m.Unlock() + + for key, conflict := range keys { + // Sanity check. Verify that the store agrees that this key is expired. + if store.Expiration(key).After(now) { + continue + } + + cost := policy.Cost(key) + policy.Del(key) + _, value := store.Del(key, conflict) + + if onEvict != nil { + onEvict(&Item{Key: key, + Conflict: conflict, + Value: value, + Cost: cost, + }) + } + } +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/LICENSE b/vendor/github.com/dgraph-io/ristretto/z/LICENSE new file mode 100644 index 0000000000..0860cbfe85 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/LICENSE @@ -0,0 +1,64 @@ +bbloom.go + +// The MIT License (MIT) +// Copyright (c) 2014 Andreas Briese, eduToolbox@Bri-C GmbH, Sarstedt + +// 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. + +rtutil.go + +// MIT License + +// Copyright (c) 2019 Ewan Chou + +// 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. + +Modifications: + +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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. + */ + diff --git a/vendor/github.com/dgraph-io/ristretto/z/README.md b/vendor/github.com/dgraph-io/ristretto/z/README.md new file mode 100644 index 0000000000..6d77e146eb --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/README.md @@ -0,0 +1,129 @@ +## bbloom: a bitset Bloom filter for go/golang +=== + +package implements a fast bloom filter with real 'bitset' and JSONMarshal/JSONUnmarshal to store/reload the Bloom filter. + +NOTE: the package uses unsafe.Pointer to set and read the bits from the bitset. If you're uncomfortable with using the unsafe package, please consider using my bloom filter package at github.com/AndreasBriese/bloom + +=== + +changelog 11/2015: new thread safe methods AddTS(), HasTS(), AddIfNotHasTS() following a suggestion from Srdjan Marinovic (github @a-little-srdjan), who used this to code a bloomfilter cache. + +This bloom filter was developed to strengthen a website-log database and was tested and optimized for this log-entry mask: "2014/%02i/%02i %02i:%02i:%02i /info.html". +Nonetheless bbloom should work with any other form of entries. + +~~Hash function is a modified Berkeley DB sdbm hash (to optimize for smaller strings). sdbm http://www.cse.yorku.ca/~oz/hash.html~~ + +Found sipHash (SipHash-2-4, a fast short-input PRF created by Jean-Philippe Aumasson and Daniel J. Bernstein.) to be about as fast. sipHash had been ported by Dimtry Chestnyk to Go (github.com/dchest/siphash ) + +Minimum hashset size is: 512 ([4]uint64; will be set automatically). + +###install + +```sh +go get github.com/AndreasBriese/bbloom +``` + +###test ++ change to folder ../bbloom ++ create wordlist in file "words.txt" (you might use `python permut.py`) ++ run 'go test -bench=.' within the folder + +```go +go test -bench=. +``` + +~~If you've installed the GOCONVEY TDD-framework http://goconvey.co/ you can run the tests automatically.~~ + +using go's testing framework now (have in mind that the op timing is related to 65536 operations of Add, Has, AddIfNotHas respectively) + +### usage + +after installation add + +```go +import ( + ... + "github.com/AndreasBriese/bbloom" + ... + ) +``` + +at your header. In the program use + +```go +// create a bloom filter for 65536 items and 1 % wrong-positive ratio +bf := bbloom.New(float64(1<<16), float64(0.01)) + +// or +// create a bloom filter with 650000 for 65536 items and 7 locs per hash explicitly +// bf = bbloom.New(float64(650000), float64(7)) +// or +bf = bbloom.New(650000.0, 7.0) + +// add one item +bf.Add([]byte("butter")) + +// Number of elements added is exposed now +// Note: ElemNum will not be included in JSON export (for compatability to older version) +nOfElementsInFilter := bf.ElemNum + +// check if item is in the filter +isIn := bf.Has([]byte("butter")) // should be true +isNotIn := bf.Has([]byte("Butter")) // should be false + +// 'add only if item is new' to the bloomfilter +added := bf.AddIfNotHas([]byte("butter")) // should be false because 'butter' is already in the set +added = bf.AddIfNotHas([]byte("buTTer")) // should be true because 'buTTer' is new + +// thread safe versions for concurrent use: AddTS, HasTS, AddIfNotHasTS +// add one item +bf.AddTS([]byte("peanutbutter")) +// check if item is in the filter +isIn = bf.HasTS([]byte("peanutbutter")) // should be true +isNotIn = bf.HasTS([]byte("peanutButter")) // should be false +// 'add only if item is new' to the bloomfilter +added = bf.AddIfNotHasTS([]byte("butter")) // should be false because 'peanutbutter' is already in the set +added = bf.AddIfNotHasTS([]byte("peanutbuTTer")) // should be true because 'penutbuTTer' is new + +// convert to JSON ([]byte) +Json := bf.JSONMarshal() + +// bloomfilters Mutex is exposed for external un-/locking +// i.e. mutex lock while doing JSON conversion +bf.Mtx.Lock() +Json = bf.JSONMarshal() +bf.Mtx.Unlock() + +// restore a bloom filter from storage +bfNew := bbloom.JSONUnmarshal(Json) + +isInNew := bfNew.Has([]byte("butter")) // should be true +isNotInNew := bfNew.Has([]byte("Butter")) // should be false + +``` + +to work with the bloom filter. + +### why 'fast'? + +It's about 3 times faster than William Fitzgeralds bitset bloom filter https://github.com/willf/bloom . And it is about so fast as my []bool set variant for Boom filters (see https://github.com/AndreasBriese/bloom ) but having a 8times smaller memory footprint: + + + Bloom filter (filter size 524288, 7 hashlocs) + github.com/AndreasBriese/bbloom 'Add' 65536 items (10 repetitions): 6595800 ns (100 ns/op) + github.com/AndreasBriese/bbloom 'Has' 65536 items (10 repetitions): 5986600 ns (91 ns/op) + github.com/AndreasBriese/bloom 'Add' 65536 items (10 repetitions): 6304684 ns (96 ns/op) + github.com/AndreasBriese/bloom 'Has' 65536 items (10 repetitions): 6568663 ns (100 ns/op) + + github.com/willf/bloom 'Add' 65536 items (10 repetitions): 24367224 ns (371 ns/op) + github.com/willf/bloom 'Test' 65536 items (10 repetitions): 21881142 ns (333 ns/op) + github.com/dataence/bloom/standard 'Add' 65536 items (10 repetitions): 23041644 ns (351 ns/op) + github.com/dataence/bloom/standard 'Check' 65536 items (10 repetitions): 19153133 ns (292 ns/op) + github.com/cabello/bloom 'Add' 65536 items (10 repetitions): 131921507 ns (2012 ns/op) + github.com/cabello/bloom 'Contains' 65536 items (10 repetitions): 131108962 ns (2000 ns/op) + +(on MBPro15 OSX10.8.5 i7 4Core 2.4Ghz) + + +With 32bit bloom filters (bloom32) using modified sdbm, bloom32 does hashing with only 2 bit shifts, one xor and one substraction per byte. smdb is about as fast as fnv64a but gives less collisions with the dataset (see mask above). bloom.New(float64(10 * 1<<16),float64(7)) populated with 1<<16 random items from the dataset (see above) and tested against the rest results in less than 0.05% collisions. diff --git a/vendor/github.com/dgraph-io/ristretto/z/allocator.go b/vendor/github.com/dgraph-io/ristretto/z/allocator.go new file mode 100644 index 0000000000..db00ff5eca --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/allocator.go @@ -0,0 +1,403 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "bytes" + "fmt" + "math" + "math/bits" + "math/rand" + "strings" + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/dustin/go-humanize" +) + +// Allocator amortizes the cost of small allocations by allocating memory in +// bigger chunks. Internally it uses z.Calloc to allocate memory. Once +// allocated, the memory is not moved, so it is safe to use the allocated bytes +// to unsafe cast them to Go struct pointers. Maintaining a freelist is slow. +// Instead, Allocator only allocates memory, with the idea that finally we +// would just release the entire Allocator. +type Allocator struct { + sync.Mutex + compIdx uint64 // Stores bufIdx in 32 MSBs and posIdx in 32 LSBs. + buffers [][]byte + Ref uint64 + Tag string +} + +// allocs keeps references to all Allocators, so we can safely discard them later. +var allocsMu *sync.Mutex +var allocRef uint64 +var allocs map[uint64]*Allocator +var calculatedLog2 []int + +func init() { + allocsMu = new(sync.Mutex) + allocs = make(map[uint64]*Allocator) + + // Set up a unique Ref per process. + rand.Seed(time.Now().UnixNano()) + allocRef = uint64(rand.Int63n(1<<16)) << 48 + + calculatedLog2 = make([]int, 1025) + for i := 1; i <= 1024; i++ { + calculatedLog2[i] = int(math.Log2(float64(i))) + } +} + +// NewAllocator creates an allocator starting with the given size. +func NewAllocator(sz int, tag string) *Allocator { + ref := atomic.AddUint64(&allocRef, 1) + // We should not allow a zero sized page because addBufferWithMinSize + // will run into an infinite loop trying to double the pagesize. + if sz < 512 { + sz = 512 + } + a := &Allocator{ + Ref: ref, + buffers: make([][]byte, 64), + Tag: tag, + } + l2 := uint64(log2(sz)) + if bits.OnesCount64(uint64(sz)) > 1 { + l2 += 1 + } + a.buffers[0] = Calloc(1<> 32), int(pos & 0xFFFFFFFF) +} + +// Size returns the size of the allocations so far. +func (a *Allocator) Size() int { + pos := atomic.LoadUint64(&a.compIdx) + bi, pi := parse(pos) + var sz int + for i, b := range a.buffers { + if i < bi { + sz += len(b) + continue + } + sz += pi + return sz + } + panic("Size should not reach here") +} + +func log2(sz int) int { + if sz < len(calculatedLog2) { + return calculatedLog2[sz] + } + pow := 10 + sz >>= 10 + for sz > 1 { + sz >>= 1 + pow++ + } + return pow +} + +func (a *Allocator) Allocated() uint64 { + var alloc int + for _, b := range a.buffers { + alloc += cap(b) + } + return uint64(alloc) +} + +func (a *Allocator) TrimTo(max int) { + var alloc int + for i, b := range a.buffers { + if len(b) == 0 { + break + } + alloc += len(b) + if alloc < max { + continue + } + Free(b) + a.buffers[i] = nil + } +} + +// Release would release the memory back. Remember to make this call to avoid memory leaks. +func (a *Allocator) Release() { + if a == nil { + return + } + + var alloc int + for _, b := range a.buffers { + if len(b) == 0 { + break + } + alloc += len(b) + Free(b) + } + + allocsMu.Lock() + delete(allocs, a.Ref) + allocsMu.Unlock() +} + +const maxAlloc = 1 << 30 + +func (a *Allocator) MaxAlloc() int { + return maxAlloc +} + +const nodeAlign = unsafe.Sizeof(uint64(0)) - 1 + +func (a *Allocator) AllocateAligned(sz int) []byte { + tsz := sz + int(nodeAlign) + out := a.Allocate(tsz) + // We are reusing allocators. In that case, it's important to zero out the memory allocated + // here. We don't always zero it out (in Allocate), because other functions would be immediately + // overwriting the allocated slices anyway (see Copy). + ZeroOut(out, 0, len(out)) + + addr := uintptr(unsafe.Pointer(&out[0])) + aligned := (addr + nodeAlign) & ^nodeAlign + start := int(aligned - addr) + + return out[start : start+sz] +} + +func (a *Allocator) Copy(buf []byte) []byte { + if a == nil { + return append([]byte{}, buf...) + } + out := a.Allocate(len(buf)) + copy(out, buf) + return out +} + +func (a *Allocator) addBufferAt(bufIdx, minSz int) { + for { + if bufIdx >= len(a.buffers) { + panic(fmt.Sprintf("Allocator can not allocate more than %d buffers", len(a.buffers))) + } + if len(a.buffers[bufIdx]) == 0 { + break + } + if minSz <= len(a.buffers[bufIdx]) { + // No need to do anything. We already have a buffer which can satisfy minSz. + return + } + bufIdx++ + } + assert(bufIdx > 0) + // We need to allocate a new buffer. + // Make pageSize double of the last allocation. + pageSize := 2 * len(a.buffers[bufIdx-1]) + // Ensure pageSize is bigger than sz. + for pageSize < minSz { + pageSize *= 2 + } + // If bigger than maxAlloc, trim to maxAlloc. + if pageSize > maxAlloc { + pageSize = maxAlloc + } + + buf := Calloc(pageSize, a.Tag) + assert(len(a.buffers[bufIdx]) == 0) + a.buffers[bufIdx] = buf +} + +func (a *Allocator) Allocate(sz int) []byte { + if a == nil { + return make([]byte, sz) + } + if sz > maxAlloc { + panic(fmt.Sprintf("Unable to allocate more than %d\n", maxAlloc)) + } + if sz == 0 { + return nil + } + for { + pos := atomic.AddUint64(&a.compIdx, uint64(sz)) + bufIdx, posIdx := parse(pos) + buf := a.buffers[bufIdx] + if posIdx > len(buf) { + a.Lock() + newPos := atomic.LoadUint64(&a.compIdx) + newBufIdx, _ := parse(newPos) + if newBufIdx != bufIdx { + a.Unlock() + continue + } + a.addBufferAt(bufIdx+1, sz) + atomic.StoreUint64(&a.compIdx, uint64((bufIdx+1)<<32)) + a.Unlock() + // We added a new buffer. Let's acquire slice the right way by going back to the top. + continue + } + data := buf[posIdx-sz : posIdx] + return data + } +} + +type AllocatorPool struct { + numGets int64 + allocCh chan *Allocator + closer *Closer +} + +func NewAllocatorPool(sz int) *AllocatorPool { + a := &AllocatorPool{ + allocCh: make(chan *Allocator, sz), + closer: NewCloser(1), + } + go a.freeupAllocators() + return a +} + +func (p *AllocatorPool) Get(sz int, tag string) *Allocator { + if p == nil { + return NewAllocator(sz, tag) + } + atomic.AddInt64(&p.numGets, 1) + select { + case alloc := <-p.allocCh: + alloc.Reset() + alloc.Tag = tag + return alloc + default: + return NewAllocator(sz, tag) + } +} +func (p *AllocatorPool) Return(a *Allocator) { + if a == nil { + return + } + if p == nil { + a.Release() + return + } + a.TrimTo(400 << 20) + + select { + case p.allocCh <- a: + return + default: + a.Release() + } +} + +func (p *AllocatorPool) Release() { + if p == nil { + return + } + p.closer.SignalAndWait() +} + +func (p *AllocatorPool) freeupAllocators() { + defer p.closer.Done() + + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + releaseOne := func() bool { + select { + case alloc := <-p.allocCh: + alloc.Release() + return true + default: + return false + } + } + + var last int64 + for { + select { + case <-p.closer.HasBeenClosed(): + close(p.allocCh) + for alloc := range p.allocCh { + alloc.Release() + } + return + + case <-ticker.C: + gets := atomic.LoadInt64(&p.numGets) + if gets != last { + // Some retrievals were made since the last time. So, let's avoid doing a release. + last = gets + continue + } + releaseOne() + } + } +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/bbloom.go b/vendor/github.com/dgraph-io/ristretto/z/bbloom.go new file mode 100644 index 0000000000..37135b012f --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/bbloom.go @@ -0,0 +1,211 @@ +// The MIT License (MIT) +// Copyright (c) 2014 Andreas Briese, eduToolbox@Bri-C GmbH, Sarstedt + +// 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. + +package z + +import ( + "bytes" + "encoding/json" + "math" + "unsafe" + + "github.com/golang/glog" +) + +// helper +var mask = []uint8{1, 2, 4, 8, 16, 32, 64, 128} + +func getSize(ui64 uint64) (size uint64, exponent uint64) { + if ui64 < uint64(512) { + ui64 = uint64(512) + } + size = uint64(1) + for size < ui64 { + size <<= 1 + exponent++ + } + return size, exponent +} + +func calcSizeByWrongPositives(numEntries, wrongs float64) (uint64, uint64) { + size := -1 * numEntries * math.Log(wrongs) / math.Pow(float64(0.69314718056), 2) + locs := math.Ceil(float64(0.69314718056) * size / numEntries) + return uint64(size), uint64(locs) +} + +// NewBloomFilter returns a new bloomfilter. +func NewBloomFilter(params ...float64) (bloomfilter *Bloom) { + var entries, locs uint64 + if len(params) == 2 { + if params[1] < 1 { + entries, locs = calcSizeByWrongPositives(params[0], params[1]) + } else { + entries, locs = uint64(params[0]), uint64(params[1]) + } + } else { + glog.Fatal("usage: New(float64(number_of_entries), float64(number_of_hashlocations))" + + " i.e. New(float64(1000), float64(3)) or New(float64(number_of_entries)," + + " float64(number_of_hashlocations)) i.e. New(float64(1000), float64(0.03))") + } + size, exponent := getSize(entries) + bloomfilter = &Bloom{ + sizeExp: exponent, + size: size - 1, + setLocs: locs, + shift: 64 - exponent, + } + bloomfilter.Size(size) + return bloomfilter +} + +// Bloom filter +type Bloom struct { + bitset []uint64 + ElemNum uint64 + sizeExp uint64 + size uint64 + setLocs uint64 + shift uint64 +} + +// <--- http://www.cse.yorku.ca/~oz/hash.html +// modified Berkeley DB Hash (32bit) +// hash is casted to l, h = 16bit fragments +// func (bl Bloom) absdbm(b *[]byte) (l, h uint64) { +// hash := uint64(len(*b)) +// for _, c := range *b { +// hash = uint64(c) + (hash << 6) + (hash << bl.sizeExp) - hash +// } +// h = hash >> bl.shift +// l = hash << bl.shift >> bl.shift +// return l, h +// } + +// Add adds hash of a key to the bloomfilter. +func (bl *Bloom) Add(hash uint64) { + h := hash >> bl.shift + l := hash << bl.shift >> bl.shift + for i := uint64(0); i < bl.setLocs; i++ { + bl.Set((h + i*l) & bl.size) + bl.ElemNum++ + } +} + +// Has checks if bit(s) for entry hash is/are set, +// returns true if the hash was added to the Bloom Filter. +func (bl Bloom) Has(hash uint64) bool { + h := hash >> bl.shift + l := hash << bl.shift >> bl.shift + for i := uint64(0); i < bl.setLocs; i++ { + if !bl.IsSet((h + i*l) & bl.size) { + return false + } + } + return true +} + +// AddIfNotHas only Adds hash, if it's not present in the bloomfilter. +// Returns true if hash was added. +// Returns false if hash was already registered in the bloomfilter. +func (bl *Bloom) AddIfNotHas(hash uint64) bool { + if bl.Has(hash) { + return false + } + bl.Add(hash) + return true +} + +// TotalSize returns the total size of the bloom filter. +func (bl *Bloom) TotalSize() int { + // The bl struct has 5 members and each one is 8 byte. The bitset is a + // uint64 byte slice. + return len(bl.bitset)*8 + 5*8 +} + +// Size makes Bloom filter with as bitset of size sz. +func (bl *Bloom) Size(sz uint64) { + bl.bitset = make([]uint64, sz>>6) +} + +// Clear resets the Bloom filter. +func (bl *Bloom) Clear() { + for i := range bl.bitset { + bl.bitset[i] = 0 + } +} + +// Set sets the bit[idx] of bitset. +func (bl *Bloom) Set(idx uint64) { + ptr := unsafe.Pointer(uintptr(unsafe.Pointer(&bl.bitset[idx>>6])) + uintptr((idx%64)>>3)) + *(*uint8)(ptr) |= mask[idx%8] +} + +// IsSet checks if bit[idx] of bitset is set, returns true/false. +func (bl *Bloom) IsSet(idx uint64) bool { + ptr := unsafe.Pointer(uintptr(unsafe.Pointer(&bl.bitset[idx>>6])) + uintptr((idx%64)>>3)) + r := ((*(*uint8)(ptr)) >> (idx % 8)) & 1 + return r == 1 +} + +// bloomJSONImExport +// Im/Export structure used by JSONMarshal / JSONUnmarshal +type bloomJSONImExport struct { + FilterSet []byte + SetLocs uint64 +} + +// NewWithBoolset takes a []byte slice and number of locs per entry, +// returns the bloomfilter with a bitset populated according to the input []byte. +func newWithBoolset(bs *[]byte, locs uint64) *Bloom { + bloomfilter := NewBloomFilter(float64(len(*bs)<<3), float64(locs)) + for i, b := range *bs { + *(*uint8)(unsafe.Pointer(uintptr(unsafe.Pointer(&bloomfilter.bitset[0])) + uintptr(i))) = b + } + return bloomfilter +} + +// JSONUnmarshal takes JSON-Object (type bloomJSONImExport) as []bytes +// returns bloom32 / bloom64 object. +func JSONUnmarshal(dbData []byte) (*Bloom, error) { + bloomImEx := bloomJSONImExport{} + if err := json.Unmarshal(dbData, &bloomImEx); err != nil { + return nil, err + } + buf := bytes.NewBuffer(bloomImEx.FilterSet) + bs := buf.Bytes() + bf := newWithBoolset(&bs, bloomImEx.SetLocs) + return bf, nil +} + +// JSONMarshal returns JSON-object (type bloomJSONImExport) as []byte. +func (bl Bloom) JSONMarshal() []byte { + bloomImEx := bloomJSONImExport{} + bloomImEx.SetLocs = bl.setLocs + bloomImEx.FilterSet = make([]byte, len(bl.bitset)<<3) + for i := range bloomImEx.FilterSet { + bloomImEx.FilterSet[i] = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(&bl.bitset[0])) + + uintptr(i))) + } + data, err := json.Marshal(bloomImEx) + if err != nil { + glog.Fatal("json.Marshal failed: ", err) + } + return data +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/btree.go b/vendor/github.com/dgraph-io/ristretto/z/btree.go new file mode 100644 index 0000000000..12b735bb03 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/btree.go @@ -0,0 +1,710 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "fmt" + "math" + "os" + "reflect" + "strings" + "unsafe" + + "github.com/dgraph-io/ristretto/z/simd" +) + +var ( + pageSize = os.Getpagesize() + maxKeys = (pageSize / 16) - 1 + oneThird = int(float64(maxKeys) / 3) +) + +const ( + absoluteMax = uint64(math.MaxUint64 - 1) + minSize = 1 << 20 +) + +// Tree represents the structure for custom mmaped B+ tree. +// It supports keys in range [1, math.MaxUint64-1] and values [1, math.Uint64]. +type Tree struct { + buffer *Buffer + data []byte + nextPage uint64 + freePage uint64 + stats TreeStats +} + +func (t *Tree) initRootNode() { + // This is the root node. + t.newNode(0) + // This acts as the rightmost pointer (all the keys are <= this key). + t.Set(absoluteMax, 0) +} + +// NewTree returns an in-memory B+ tree. +func NewTree(tag string) *Tree { + const defaultTag = "tree" + if tag == "" { + tag = defaultTag + } + t := &Tree{buffer: NewBuffer(minSize, tag)} + t.Reset() + return t +} + +// NewTree returns a persistent on-disk B+ tree. +func NewTreePersistent(path string) (*Tree, error) { + t := &Tree{} + var err error + + // Open the buffer from disk and set it to the maximum allocated size. + t.buffer, err = NewBufferPersistent(path, minSize) + if err != nil { + return nil, err + } + t.buffer.offset = uint64(len(t.buffer.buf)) + t.data = t.buffer.Bytes() + + // pageID can never be 0 if the tree has been initialized. + root := t.node(1) + isInitialized := root.pageID() != 0 + + if !isInitialized { + t.nextPage = 1 + t.freePage = 0 + t.initRootNode() + } else { + t.reinit() + } + + return t, nil +} + +// reinit sets the internal variables of a Tree, which are normally stored +// in-memory, but are lost when loading from disk. +func (t *Tree) reinit() { + // Calculate t.nextPage by finding the first node whose pageID is not set. + t.nextPage = 1 + for int(t.nextPage)*pageSize < len(t.data) { + n := t.node(t.nextPage) + if n.pageID() == 0 { + break + } + t.nextPage++ + } + maxPageId := t.nextPage - 1 + + // Calculate t.freePage by finding the page to which no other page points. + // This would be the head of the page linked list. + // tailPages[i] is true if pageId i+1 is not the head of the list. + tailPages := make([]bool, maxPageId) + // Mark all pages containing nodes as tail pages. + t.Iterate(func(n node) { + i := n.pageID() - 1 + tailPages[i] = true + // If this is a leaf node, increment the stats. + if n.isLeaf() { + t.stats.NumLeafKeys += n.numKeys() + } + }) + // pointedPages is a list of page IDs that the tail pages point to. + pointedPages := make([]uint64, 0) + for i, isTail := range tailPages { + if !isTail { + pageId := uint64(i) + 1 + // Skip if nextPageId = 0, as that is equivalent to null page. + if nextPageId := t.node(pageId).uint64(0); nextPageId != 0 { + pointedPages = append(pointedPages, nextPageId) + } + t.stats.NumPagesFree++ + } + } + + // Mark all pages being pointed to as tail pages. + for _, pageId := range pointedPages { + i := pageId - 1 + tailPages[i] = true + } + // There should only be one head page left. + for i, isTail := range tailPages { + if !isTail { + pageId := uint64(i) + 1 + t.freePage = pageId + break + } + } +} + +// Reset resets the tree and truncates it to maxSz. +func (t *Tree) Reset() { + // Tree relies on uninitialized data being zeroed out, so we need to Memclr + // the data before using it again. + Memclr(t.buffer.buf) + t.buffer.Reset() + t.buffer.AllocateOffset(minSize) + t.data = t.buffer.Bytes() + t.stats = TreeStats{} + t.nextPage = 1 + t.freePage = 0 + t.initRootNode() +} + +// Close releases the memory used by the tree. +func (t *Tree) Close() error { + if t == nil { + return nil + } + return t.buffer.Release() +} + +type TreeStats struct { + Allocated int // Derived. + Bytes int // Derived. + NumLeafKeys int // Calculated. + NumPages int // Derived. + NumPagesFree int // Calculated. + Occupancy float64 // Derived. + PageSize int // Derived. +} + +// Stats returns stats about the tree. +func (t *Tree) Stats() TreeStats { + numPages := int(t.nextPage - 1) + out := TreeStats{ + Bytes: numPages * pageSize, + Allocated: len(t.data), + NumLeafKeys: t.stats.NumLeafKeys, + NumPages: numPages, + NumPagesFree: t.stats.NumPagesFree, + PageSize: pageSize, + } + out.Occupancy = 100.0 * float64(out.NumLeafKeys) / float64(maxKeys*numPages) + return out +} + +// BytesToUint64Slice converts a byte slice to a uint64 slice. +func BytesToUint64Slice(b []byte) []uint64 { + if len(b) == 0 { + return nil + } + var u64s []uint64 + hdr := (*reflect.SliceHeader)(unsafe.Pointer(&u64s)) + hdr.Len = len(b) / 8 + hdr.Cap = hdr.Len + hdr.Data = uintptr(unsafe.Pointer(&b[0])) + return u64s +} + +func (t *Tree) newNode(bit uint64) node { + var pageId uint64 + if t.freePage > 0 { + pageId = t.freePage + t.stats.NumPagesFree-- + } else { + pageId = t.nextPage + t.nextPage++ + offset := int(pageId) * pageSize + reqSize := offset + pageSize + if reqSize > len(t.data) { + t.buffer.AllocateOffset(reqSize - len(t.data)) + t.data = t.buffer.Bytes() + } + } + n := t.node(pageId) + if t.freePage > 0 { + t.freePage = n.uint64(0) + } + zeroOut(n) + n.setBit(bit) + n.setAt(keyOffset(maxKeys), pageId) + return n +} + +func getNode(data []byte) node { + return node(BytesToUint64Slice(data)) +} + +func zeroOut(data []uint64) { + for i := 0; i < len(data); i++ { + data[i] = 0 + } +} + +func (t *Tree) node(pid uint64) node { + // page does not exist + if pid == 0 { + return nil + } + start := pageSize * int(pid) + return getNode(t.data[start : start+pageSize]) +} + +// Set sets the key-value pair in the tree. +func (t *Tree) Set(k, v uint64) { + if k == math.MaxUint64 || k == 0 { + panic("Error setting zero or MaxUint64") + } + root := t.set(1, k, v) + if root.isFull() { + right := t.split(1) + left := t.newNode(root.bits()) + // Re-read the root as the underlying buffer for tree might have changed during split. + root = t.node(1) + copy(left[:keyOffset(maxKeys)], root) + left.setNumKeys(root.numKeys()) + + // reset the root node. + zeroOut(root[:keyOffset(maxKeys)]) + root.setNumKeys(0) + + // set the pointers for left and right child in the root node. + root.set(left.maxKey(), left.pageID()) + root.set(right.maxKey(), right.pageID()) + } +} + +// For internal nodes, they contain . +// where all entries <= key are stored in the corresponding ptr. +func (t *Tree) set(pid, k, v uint64) node { + n := t.node(pid) + if n.isLeaf() { + t.stats.NumLeafKeys += n.set(k, v) + return n + } + + // This is an internal node. + idx := n.search(k) + if idx >= maxKeys { + panic("search returned index >= maxKeys") + } + // If no key at idx. + if n.key(idx) == 0 { + n.setAt(keyOffset(idx), k) + n.setNumKeys(n.numKeys() + 1) + } + child := t.node(n.val(idx)) + if child == nil { + child = t.newNode(bitLeaf) + n = t.node(pid) + n.setAt(valOffset(idx), child.pageID()) + } + child = t.set(child.pageID(), k, v) + // Re-read n as the underlying buffer for tree might have changed during set. + n = t.node(pid) + if child.isFull() { + // Just consider the left sibling for simplicity. + // if t.shareWithSibling(n, idx) { + // return n + // } + + nn := t.split(child.pageID()) + // Re-read n and child as the underlying buffer for tree might have changed during split. + n = t.node(pid) + child = t.node(n.uint64(valOffset(idx))) + // Set child pointers in the node n. + // Note that key for right node (nn) already exist in node n, but the + // pointer is updated. + n.set(child.maxKey(), child.pageID()) + n.set(nn.maxKey(), nn.pageID()) + } + return n +} + +// Get looks for key and returns the corresponding value. +// If key is not found, 0 is returned. +func (t *Tree) Get(k uint64) uint64 { + if k == math.MaxUint64 || k == 0 { + panic("Does not support getting MaxUint64/Zero") + } + root := t.node(1) + return t.get(root, k) +} + +func (t *Tree) get(n node, k uint64) uint64 { + if n.isLeaf() { + return n.get(k) + } + // This is internal node + idx := n.search(k) + if idx == n.numKeys() || n.key(idx) == 0 { + return 0 + } + child := t.node(n.uint64(valOffset(idx))) + assert(child != nil) + return t.get(child, k) +} + +// DeleteBelow deletes all keys with value under ts. +func (t *Tree) DeleteBelow(ts uint64) { + root := t.node(1) + t.stats.NumLeafKeys = 0 + t.compact(root, ts) + assert(root.numKeys() >= 1) +} + +func (t *Tree) compact(n node, ts uint64) int { + if n.isLeaf() { + numKeys := n.compact(ts) + t.stats.NumLeafKeys += n.numKeys() + return numKeys + } + // Not leaf. + N := n.numKeys() + for i := 0; i < N; i++ { + assert(n.key(i) > 0) + childID := n.uint64(valOffset(i)) + child := t.node(childID) + if rem := t.compact(child, ts); rem == 0 && i < N-1 { + // If no valid key is remaining we can drop this child. However, don't do that if this + // is the max key. + t.stats.NumLeafKeys -= child.numKeys() + child.setAt(0, t.freePage) + t.freePage = childID + n.setAt(valOffset(i), 0) + t.stats.NumPagesFree++ + } + } + // We use ts=1 here because we want to delete all the keys whose value is 0, which means they no + // longer have a valid page for that key. + return n.compact(1) +} + +func (t *Tree) iterate(n node, fn func(node)) { + fn(n) + if n.isLeaf() { + return + } + // Explore children. + for i := 0; i < maxKeys; i++ { + if n.key(i) == 0 { + return + } + childID := n.uint64(valOffset(i)) + assert(childID > 0) + + child := t.node(childID) + t.iterate(child, fn) + } +} + +// Iterate iterates over the tree and executes the fn on each node. +func (t *Tree) Iterate(fn func(node)) { + root := t.node(1) + t.iterate(root, fn) +} + +// IterateKV iterates through all keys and values in the tree. +// If newVal is non-zero, it will be set in the tree. +func (t *Tree) IterateKV(f func(key, val uint64) (newVal uint64)) { + t.Iterate(func(n node) { + // Only leaf nodes contain keys. + if !n.isLeaf() { + return + } + + for i := 0; i < n.numKeys(); i++ { + key := n.key(i) + val := n.val(i) + + // A zero value here means that this is a bogus entry. + if val == 0 { + continue + } + + newVal := f(key, val) + if newVal != 0 { + n.setAt(valOffset(i), newVal) + } + } + }) +} + +func (t *Tree) print(n node, parentID uint64) { + n.print(parentID) + if n.isLeaf() { + return + } + pid := n.pageID() + for i := 0; i < maxKeys; i++ { + if n.key(i) == 0 { + return + } + childID := n.uint64(valOffset(i)) + child := t.node(childID) + t.print(child, pid) + } +} + +// Print iterates over the tree and prints all valid KVs. +func (t *Tree) Print() { + root := t.node(1) + t.print(root, 0) +} + +// Splits the node into two. It moves right half of the keys from the original node to a newly +// created right node. It returns the right node. +func (t *Tree) split(pid uint64) node { + n := t.node(pid) + if !n.isFull() { + panic("This should be called only when n is full") + } + + // Create a new node nn, copy over half the keys from n, and set the parent to n's parent. + nn := t.newNode(n.bits()) + // Re-read n as the underlying buffer for tree might have changed during newNode. + n = t.node(pid) + rightHalf := n[keyOffset(maxKeys/2):keyOffset(maxKeys)] + copy(nn, rightHalf) + nn.setNumKeys(maxKeys - maxKeys/2) + + // Remove entries from node n. + zeroOut(rightHalf) + n.setNumKeys(maxKeys / 2) + return nn +} + +// shareWithSiblingXXX is unused for now. The idea is to move some keys to +// sibling when a node is full. But, I don't see any special benefits in our +// access pattern. It doesn't result in better occupancy ratios. +func (t *Tree) shareWithSiblingXXX(n node, idx int) bool { + if idx == 0 { + return false + } + left := t.node(n.val(idx - 1)) + ns := left.numKeys() + if ns >= maxKeys/2 { + // Sibling is already getting full. + return false + } + + right := t.node(n.val(idx)) + // Copy over keys from right child to left child. + copied := copy(left[keyOffset(ns):], right[:keyOffset(oneThird)]) + copied /= 2 // Considering that key-val constitute one key. + left.setNumKeys(ns + copied) + + // Update the max key in parent node n for the left sibling. + n.setAt(keyOffset(idx-1), left.maxKey()) + + // Now move keys to left for the right sibling. + until := copy(right, right[keyOffset(oneThird):keyOffset(maxKeys)]) + right.setNumKeys(until / 2) + zeroOut(right[until:keyOffset(maxKeys)]) + return true +} + +// Each node in the node is of size pageSize. Two kinds of nodes. Leaf nodes and internal nodes. +// Leaf nodes only contain the data. Internal nodes would contain the key and the offset to the +// child node. +// Internal node would have first entry as +// <0 offset to child>, <1000 offset>, <5000 offset>, and so on... +// Leaf nodes would just have: , , and so on... +// Last 16 bytes of the node are off limits. +// | pageID (8 bytes) | metaBits (1 byte) | 3 free bytes | numKeys (4 bytes) | +type node []uint64 + +func (n node) uint64(start int) uint64 { return n[start] } + +// func (n node) uint32(start int) uint32 { return *(*uint32)(unsafe.Pointer(&n[start])) } + +func keyOffset(i int) int { return 2 * i } +func valOffset(i int) int { return 2*i + 1 } +func (n node) numKeys() int { return int(n.uint64(valOffset(maxKeys)) & 0xFFFFFFFF) } +func (n node) pageID() uint64 { return n.uint64(keyOffset(maxKeys)) } +func (n node) key(i int) uint64 { return n.uint64(keyOffset(i)) } +func (n node) val(i int) uint64 { return n.uint64(valOffset(i)) } +func (n node) data(i int) []uint64 { return n[keyOffset(i):keyOffset(i+1)] } + +func (n node) setAt(start int, k uint64) { + n[start] = k +} + +func (n node) setNumKeys(num int) { + idx := valOffset(maxKeys) + val := n[idx] + val &= 0xFFFFFFFF00000000 + val |= uint64(num) + n[idx] = val +} + +func (n node) moveRight(lo int) { + hi := n.numKeys() + assert(hi != maxKeys) + // copy works despite of overlap in src and dst. + // See https://golang.org/pkg/builtin/#copy + copy(n[keyOffset(lo+1):keyOffset(hi+1)], n[keyOffset(lo):keyOffset(hi)]) +} + +const ( + bitLeaf = uint64(1 << 63) +) + +func (n node) setBit(b uint64) { + vo := valOffset(maxKeys) + val := n[vo] + val &= 0xFFFFFFFF + val |= b + n[vo] = val +} +func (n node) bits() uint64 { + return n.val(maxKeys) & 0xFF00000000000000 +} +func (n node) isLeaf() bool { + return n.bits()&bitLeaf > 0 +} + +// isFull checks that the node is already full. +func (n node) isFull() bool { + return n.numKeys() == maxKeys +} + +// Search returns the index of a smallest key >= k in a node. +func (n node) search(k uint64) int { + N := n.numKeys() + if N < 4 { + for i := 0; i < N; i++ { + if ki := n.key(i); ki >= k { + return i + } + } + return N + } + return int(simd.Search(n[:2*N], k)) + // lo, hi := 0, N + // // Reduce the search space using binary seach and then do linear search. + // for hi-lo > 32 { + // mid := (hi + lo) / 2 + // km := n.key(mid) + // if k == km { + // return mid + // } + // if k > km { + // // key is greater than the key at mid, so move right. + // lo = mid + 1 + // } else { + // // else move left. + // hi = mid + // } + // } + // for i := lo; i <= hi; i++ { + // if ki := n.key(i); ki >= k { + // return i + // } + // } + // return N +} +func (n node) maxKey() uint64 { + idx := n.numKeys() + // idx points to the first key which is zero. + if idx > 0 { + idx-- + } + return n.key(idx) +} + +// compacts the node i.e., remove all the kvs with value < lo. It returns the remaining number of +// keys. +func (n node) compact(lo uint64) int { + N := n.numKeys() + mk := n.maxKey() + var left, right int + for right = 0; right < N; right++ { + if n.val(right) < lo && n.key(right) < mk { + // Skip over this key. Don't copy it. + continue + } + // Valid data. Copy it from right to left. Advance left. + if left != right { + copy(n.data(left), n.data(right)) + } + left++ + } + // zero out rest of the kv pairs. + zeroOut(n[keyOffset(left):keyOffset(right)]) + n.setNumKeys(left) + + // If the only key we have is the max key, and its value is less than lo, then we can indicate + // to the caller by returning a zero that it's OK to drop the node. + if left == 1 && n.key(0) == mk && n.val(0) < lo { + return 0 + } + return left +} + +func (n node) get(k uint64) uint64 { + idx := n.search(k) + // key is not found + if idx == n.numKeys() { + return 0 + } + if ki := n.key(idx); ki == k { + return n.val(idx) + } + return 0 +} + +// set returns true if it added a new key. +func (n node) set(k, v uint64) (numAdded int) { + idx := n.search(k) + ki := n.key(idx) + if n.numKeys() == maxKeys { + // This happens during split of non-root node, when we are updating the child pointer of + // right node. Hence, the key should already exist. + assert(ki == k) + } + if ki > k { + // Found the first entry which is greater than k. So, we need to fit k + // just before it. For that, we should move the rest of the data in the + // node to the right to make space for k. + n.moveRight(idx) + } + // If the k does not exist already, increment the number of keys. + if ki != k { + n.setNumKeys(n.numKeys() + 1) + numAdded = 1 + } + if ki == 0 || ki >= k { + n.setAt(keyOffset(idx), k) + n.setAt(valOffset(idx), v) + return + } + panic("shouldn't reach here") +} + +func (n node) iterate(fn func(node, int)) { + for i := 0; i < maxKeys; i++ { + if k := n.key(i); k > 0 { + fn(n, i) + } else { + break + } + } +} + +func (n node) print(parentID uint64) { + var keys []string + n.iterate(func(n node, i int) { + keys = append(keys, fmt.Sprintf("%d", n.key(i))) + }) + if len(keys) > 8 { + copy(keys[4:], keys[len(keys)-4:]) + keys[3] = "..." + keys = keys[:8] + } + fmt.Printf("%d Child of: %d num keys: %d keys: %s\n", + n.pageID(), parentID, n.numKeys(), strings.Join(keys, " ")) +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/buffer.go b/vendor/github.com/dgraph-io/ristretto/z/buffer.go new file mode 100644 index 0000000000..5a22de8c7f --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/buffer.go @@ -0,0 +1,544 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "encoding/binary" + "fmt" + "io/ioutil" + "os" + "sort" + "sync/atomic" + + "github.com/golang/glog" + "github.com/pkg/errors" +) + +const ( + defaultCapacity = 64 + defaultTag = "buffer" +) + +// Buffer is equivalent of bytes.Buffer without the ability to read. It is NOT thread-safe. +// +// In UseCalloc mode, z.Calloc is used to allocate memory, which depending upon how the code is +// compiled could use jemalloc for allocations. +// +// In UseMmap mode, Buffer uses file mmap to allocate memory. This allows us to store big data +// structures without using physical memory. +// +// MaxSize can be set to limit the memory usage. +type Buffer struct { + padding uint64 // number of starting bytes used for padding + offset uint64 // used length of the buffer + buf []byte // backing slice for the buffer + bufType BufferType // type of the underlying buffer + curSz int // capacity of the buffer + maxSz int // causes a panic if the buffer grows beyond this size + mmapFile *MmapFile // optional mmap backing for the buffer + autoMmapAfter int // Calloc falls back to an mmaped tmpfile after crossing this size + autoMmapDir string // directory for autoMmap to create a tempfile in + persistent bool // when enabled, Release will not delete the underlying mmap file + tag string // used for jemalloc stats +} + +func NewBuffer(capacity int, tag string) *Buffer { + if capacity < defaultCapacity { + capacity = defaultCapacity + } + if tag == "" { + tag = defaultTag + } + return &Buffer{ + buf: Calloc(capacity, tag), + bufType: UseCalloc, + curSz: capacity, + offset: 8, + padding: 8, + tag: tag, + } +} + +// It is the caller's responsibility to set offset after this, because Buffer +// doesn't remember what it was. +func NewBufferPersistent(path string, capacity int) (*Buffer, error) { + file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return nil, err + } + buffer, err := newBufferFile(file, capacity) + if err != nil { + return nil, err + } + buffer.persistent = true + return buffer, nil +} + +func NewBufferTmp(dir string, capacity int) (*Buffer, error) { + if dir == "" { + dir = tmpDir + } + file, err := ioutil.TempFile(dir, "buffer") + if err != nil { + return nil, err + } + return newBufferFile(file, capacity) +} + +func newBufferFile(file *os.File, capacity int) (*Buffer, error) { + if capacity < defaultCapacity { + capacity = defaultCapacity + } + mmapFile, err := OpenMmapFileUsing(file, capacity, true) + if err != nil && err != NewFile { + return nil, err + } + buf := &Buffer{ + buf: mmapFile.Data, + bufType: UseMmap, + curSz: len(mmapFile.Data), + mmapFile: mmapFile, + offset: 8, + padding: 8, + } + return buf, nil +} + +func NewBufferSlice(slice []byte) *Buffer { + return &Buffer{ + offset: uint64(len(slice)), + buf: slice, + bufType: UseInvalid, + } +} + +func (b *Buffer) WithAutoMmap(threshold int, path string) *Buffer { + if b.bufType != UseCalloc { + panic("can only autoMmap with UseCalloc") + } + b.autoMmapAfter = threshold + if path == "" { + b.autoMmapDir = tmpDir + } else { + b.autoMmapDir = path + } + return b +} + +func (b *Buffer) WithMaxSize(size int) *Buffer { + b.maxSz = size + return b +} + +func (b *Buffer) IsEmpty() bool { + return int(b.offset) == b.StartOffset() +} + +// LenWithPadding would return the number of bytes written to the buffer so far +// plus the padding at the start of the buffer. +func (b *Buffer) LenWithPadding() int { + return int(atomic.LoadUint64(&b.offset)) +} + +// LenNoPadding would return the number of bytes written to the buffer so far +// (without the padding). +func (b *Buffer) LenNoPadding() int { + return int(atomic.LoadUint64(&b.offset) - b.padding) +} + +// Bytes would return all the written bytes as a slice. +func (b *Buffer) Bytes() []byte { + off := atomic.LoadUint64(&b.offset) + return b.buf[b.padding:off] +} + +// Grow would grow the buffer to have at least n more bytes. In case the buffer is at capacity, it +// would reallocate twice the size of current capacity + n, to ensure n bytes can be written to the +// buffer without further allocation. In UseMmap mode, this might result in underlying file +// expansion. +func (b *Buffer) Grow(n int) { + if b.buf == nil { + panic("z.Buffer needs to be initialized before using") + } + if b.maxSz > 0 && int(b.offset)+n > b.maxSz { + err := fmt.Errorf( + "z.Buffer max size exceeded: %d offset: %d grow: %d", b.maxSz, b.offset, n) + panic(err) + } + if int(b.offset)+n < b.curSz { + return + } + + // Calculate new capacity. + growBy := b.curSz + n + // Don't allocate more than 1GB at a time. + if growBy > 1<<30 { + growBy = 1 << 30 + } + // Allocate at least n, even if it exceeds the 1GB limit above. + if n > growBy { + growBy = n + } + b.curSz += growBy + + switch b.bufType { + case UseCalloc: + // If autoMmap gets triggered, copy the slice over to an mmaped file. + if b.autoMmapAfter > 0 && b.curSz > b.autoMmapAfter { + b.bufType = UseMmap + file, err := ioutil.TempFile(b.autoMmapDir, "") + if err != nil { + panic(err) + } + mmapFile, err := OpenMmapFileUsing(file, b.curSz, true) + if err != nil && err != NewFile { + panic(err) + } + assert(int(b.offset) == copy(mmapFile.Data, b.buf[:b.offset])) + Free(b.buf) + b.mmapFile = mmapFile + b.buf = mmapFile.Data + break + } + + // Else, reallocate the slice. + newBuf := Calloc(b.curSz, b.tag) + assert(int(b.offset) == copy(newBuf, b.buf[:b.offset])) + Free(b.buf) + b.buf = newBuf + + case UseMmap: + // Truncate and remap the underlying file. + if err := b.mmapFile.Truncate(int64(b.curSz)); err != nil { + err = errors.Wrapf(err, + "while trying to truncate file: %s to size: %d", b.mmapFile.Fd.Name(), b.curSz) + panic(err) + } + b.buf = b.mmapFile.Data + + default: + panic("can only use Grow on UseCalloc and UseMmap buffers") + } +} + +// Allocate is a way to get a slice of size n back from the buffer. This slice can be directly +// written to. Warning: Allocate is not thread-safe. The byte slice returned MUST be used before +// further calls to Buffer. +func (b *Buffer) Allocate(n int) []byte { + b.Grow(n) + off := b.offset + b.offset += uint64(n) + return b.buf[off:int(b.offset)] +} + +// AllocateOffset works the same way as allocate, but instead of returning a byte slice, it returns +// the offset of the allocation. +func (b *Buffer) AllocateOffset(n int) int { + b.Grow(n) + b.offset += uint64(n) + return int(b.offset) - n +} + +func (b *Buffer) writeLen(sz int) { + buf := b.Allocate(4) + binary.BigEndian.PutUint32(buf, uint32(sz)) +} + +// SliceAllocate would encode the size provided into the buffer, followed by a call to Allocate, +// hence returning the slice of size sz. This can be used to allocate a lot of small buffers into +// this big buffer. +// Note that SliceAllocate should NOT be mixed with normal calls to Write. +func (b *Buffer) SliceAllocate(sz int) []byte { + b.Grow(4 + sz) + b.writeLen(sz) + return b.Allocate(sz) +} + +func (b *Buffer) StartOffset() int { + return int(b.padding) +} + +func (b *Buffer) WriteSlice(slice []byte) { + dst := b.SliceAllocate(len(slice)) + assert(len(slice) == copy(dst, slice)) +} + +func (b *Buffer) SliceIterate(f func(slice []byte) error) error { + if b.IsEmpty() { + return nil + } + slice, next := []byte{}, b.StartOffset() + for next >= 0 { + slice, next = b.Slice(next) + if len(slice) == 0 { + continue + } + if err := f(slice); err != nil { + return err + } + } + return nil +} + +const ( + UseCalloc BufferType = iota + UseMmap + UseInvalid +) + +type BufferType int + +func (t BufferType) String() string { + switch t { + case UseCalloc: + return "UseCalloc" + case UseMmap: + return "UseMmap" + default: + return "UseInvalid" + } +} + +type LessFunc func(a, b []byte) bool +type sortHelper struct { + offsets []int + b *Buffer + tmp *Buffer + less LessFunc + small []int +} + +func (s *sortHelper) sortSmall(start, end int) { + s.tmp.Reset() + s.small = s.small[:0] + next := start + for next >= 0 && next < end { + s.small = append(s.small, next) + _, next = s.b.Slice(next) + } + + // We are sorting the slices pointed to by s.small offsets, but only moving the offsets around. + sort.Slice(s.small, func(i, j int) bool { + left, _ := s.b.Slice(s.small[i]) + right, _ := s.b.Slice(s.small[j]) + return s.less(left, right) + }) + // Now we iterate over the s.small offsets and copy over the slices. The result is now in order. + for _, off := range s.small { + s.tmp.Write(rawSlice(s.b.buf[off:])) + } + assert(end-start == copy(s.b.buf[start:end], s.tmp.Bytes())) +} + +func assert(b bool) { + if !b { + glog.Fatalf("%+v", errors.Errorf("Assertion failure")) + } +} +func check(err error) { + if err != nil { + glog.Fatalf("%+v", err) + } +} +func check2(_ interface{}, err error) { + check(err) +} + +func (s *sortHelper) merge(left, right []byte, start, end int) { + if len(left) == 0 || len(right) == 0 { + return + } + s.tmp.Reset() + check2(s.tmp.Write(left)) + left = s.tmp.Bytes() + + var ls, rs []byte + + copyLeft := func() { + assert(len(ls) == copy(s.b.buf[start:], ls)) + left = left[len(ls):] + start += len(ls) + } + copyRight := func() { + assert(len(rs) == copy(s.b.buf[start:], rs)) + right = right[len(rs):] + start += len(rs) + } + + for start < end { + if len(left) == 0 { + assert(len(right) == copy(s.b.buf[start:end], right)) + return + } + if len(right) == 0 { + assert(len(left) == copy(s.b.buf[start:end], left)) + return + } + ls = rawSlice(left) + rs = rawSlice(right) + + // We skip the first 4 bytes in the rawSlice, because that stores the length. + if s.less(ls[4:], rs[4:]) { + copyLeft() + } else { + copyRight() + } + } +} + +func (s *sortHelper) sort(lo, hi int) []byte { + assert(lo <= hi) + + mid := lo + (hi-lo)/2 + loff, hoff := s.offsets[lo], s.offsets[hi] + if lo == mid { + // No need to sort, just return the buffer. + return s.b.buf[loff:hoff] + } + + // lo, mid would sort from [offset[lo], offset[mid]) . + left := s.sort(lo, mid) + // Typically we'd use mid+1, but here mid represents an offset in the buffer. Each offset + // contains a thousand entries. So, if we do mid+1, we'd skip over those entries. + right := s.sort(mid, hi) + + s.merge(left, right, loff, hoff) + return s.b.buf[loff:hoff] +} + +// SortSlice is like SortSliceBetween but sorting over the entire buffer. +func (b *Buffer) SortSlice(less func(left, right []byte) bool) { + b.SortSliceBetween(b.StartOffset(), int(b.offset), less) +} +func (b *Buffer) SortSliceBetween(start, end int, less LessFunc) { + if start >= end { + return + } + if start == 0 { + panic("start can never be zero") + } + + var offsets []int + next, count := start, 0 + for next >= 0 && next < end { + if count%1024 == 0 { + offsets = append(offsets, next) + } + _, next = b.Slice(next) + count++ + } + assert(len(offsets) > 0) + if offsets[len(offsets)-1] != end { + offsets = append(offsets, end) + } + + szTmp := int(float64((end-start)/2) * 1.1) + s := &sortHelper{ + offsets: offsets, + b: b, + less: less, + small: make([]int, 0, 1024), + tmp: NewBuffer(szTmp, b.tag), + } + defer s.tmp.Release() + + left := offsets[0] + for _, off := range offsets[1:] { + s.sortSmall(left, off) + left = off + } + s.sort(0, len(offsets)-1) +} + +func rawSlice(buf []byte) []byte { + sz := binary.BigEndian.Uint32(buf) + return buf[:4+int(sz)] +} + +// Slice would return the slice written at offset. +func (b *Buffer) Slice(offset int) ([]byte, int) { + if offset >= int(b.offset) { + return nil, -1 + } + + sz := binary.BigEndian.Uint32(b.buf[offset:]) + start := offset + 4 + next := start + int(sz) + res := b.buf[start:next] + if next >= int(b.offset) { + next = -1 + } + return res, next +} + +// SliceOffsets is an expensive function. Use sparingly. +func (b *Buffer) SliceOffsets() []int { + next := b.StartOffset() + var offsets []int + for next >= 0 { + offsets = append(offsets, next) + _, next = b.Slice(next) + } + return offsets +} + +func (b *Buffer) Data(offset int) []byte { + if offset > b.curSz { + panic("offset beyond current size") + } + return b.buf[offset:b.curSz] +} + +// Write would write p bytes to the buffer. +func (b *Buffer) Write(p []byte) (n int, err error) { + n = len(p) + b.Grow(n) + assert(n == copy(b.buf[b.offset:], p)) + b.offset += uint64(n) + return n, nil +} + +// Reset would reset the buffer to be reused. +func (b *Buffer) Reset() { + b.offset = uint64(b.StartOffset()) +} + +// Release would free up the memory allocated by the buffer. Once the usage of buffer is done, it is +// important to call Release, otherwise a memory leak can happen. +func (b *Buffer) Release() error { + if b == nil { + return nil + } + switch b.bufType { + case UseCalloc: + Free(b.buf) + case UseMmap: + if b.mmapFile == nil { + return nil + } + path := b.mmapFile.Fd.Name() + if err := b.mmapFile.Close(-1); err != nil { + return errors.Wrapf(err, "while closing file: %s", path) + } + if !b.persistent { + if err := os.Remove(path); err != nil { + return errors.Wrapf(err, "while deleting file %s", path) + } + } + } + return nil +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/calloc.go b/vendor/github.com/dgraph-io/ristretto/z/calloc.go new file mode 100644 index 0000000000..2e5d613813 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/calloc.go @@ -0,0 +1,42 @@ +package z + +import "sync/atomic" + +var numBytes int64 + +// NumAllocBytes returns the number of bytes allocated using calls to z.Calloc. The allocations +// could be happening via either Go or jemalloc, depending upon the build flags. +func NumAllocBytes() int64 { + return atomic.LoadInt64(&numBytes) +} + +// MemStats is used to fetch JE Malloc Stats. The stats are fetched from +// the mallctl namespace http://jemalloc.net/jemalloc.3.html#mallctl_namespace. +type MemStats struct { + // Total number of bytes allocated by the application. + // http://jemalloc.net/jemalloc.3.html#stats.allocated + Allocated uint64 + // Total number of bytes in active pages allocated by the application. This + // is a multiple of the page size, and greater than or equal to + // Allocated. + // http://jemalloc.net/jemalloc.3.html#stats.active + Active uint64 + // Maximum number of bytes in physically resident data pages mapped by the + // allocator, comprising all pages dedicated to allocator metadata, pages + // backing active allocations, and unused dirty pages. This is a maximum + // rather than precise because pages may not actually be physically + // resident if they correspond to demand-zeroed virtual memory that has not + // yet been touched. This is a multiple of the page size, and is larger + // than stats.active. + // http://jemalloc.net/jemalloc.3.html#stats.resident + Resident uint64 + // Total number of bytes in virtual memory mappings that were retained + // rather than being returned to the operating system via e.g. munmap(2) or + // similar. Retained virtual memory is typically untouched, decommitted, or + // purged, so it has no strongly associated physical memory (see extent + // hooks http://jemalloc.net/jemalloc.3.html#arena.i.extent_hooks for + // details). Retained memory is excluded from mapped memory statistics, + // e.g. stats.mapped (http://jemalloc.net/jemalloc.3.html#stats.mapped). + // http://jemalloc.net/jemalloc.3.html#stats.retained + Retained uint64 +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/calloc_32bit.go b/vendor/github.com/dgraph-io/ristretto/z/calloc_32bit.go new file mode 100644 index 0000000000..3a0442614f --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/calloc_32bit.go @@ -0,0 +1,14 @@ +// Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// +build 386 amd64p32 arm armbe mips mipsle mips64p32 mips64p32le ppc sparc + +package z + +const ( + // MaxArrayLen is a safe maximum length for slices on this architecture. + MaxArrayLen = 1<<31 - 1 + // MaxBufferSize is the size of virtually unlimited buffer on this architecture. + MaxBufferSize = 1 << 30 +) diff --git a/vendor/github.com/dgraph-io/ristretto/z/calloc_64bit.go b/vendor/github.com/dgraph-io/ristretto/z/calloc_64bit.go new file mode 100644 index 0000000000..b898248bba --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/calloc_64bit.go @@ -0,0 +1,14 @@ +// Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// +build amd64 arm64 arm64be ppc64 ppc64le mips64 mips64le riscv64 s390x sparc64 + +package z + +const ( + // MaxArrayLen is a safe maximum length for slices on this architecture. + MaxArrayLen = 1<<50 - 1 + // MaxBufferSize is the size of virtually unlimited buffer on this architecture. + MaxBufferSize = 256 << 30 +) diff --git a/vendor/github.com/dgraph-io/ristretto/z/calloc_jemalloc.go b/vendor/github.com/dgraph-io/ristretto/z/calloc_jemalloc.go new file mode 100644 index 0000000000..904d73ac57 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/calloc_jemalloc.go @@ -0,0 +1,172 @@ +// Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// +build jemalloc + +package z + +/* +#cgo LDFLAGS: /usr/local/lib/libjemalloc.a -L/usr/local/lib -Wl,-rpath,/usr/local/lib -ljemalloc -lm -lstdc++ -pthread -ldl +#include +#include +*/ +import "C" +import ( + "bytes" + "fmt" + "sync" + "sync/atomic" + "unsafe" + + "github.com/dustin/go-humanize" +) + +// The go:linkname directives provides backdoor access to private functions in +// the runtime. Below we're accessing the throw function. + +//go:linkname throw runtime.throw +func throw(s string) + +// New allocates a slice of size n. The returned slice is from manually managed +// memory and MUST be released by calling Free. Failure to do so will result in +// a memory leak. +// +// Compile jemalloc with ./configure --with-jemalloc-prefix="je_" +// https://android.googlesource.com/platform/external/jemalloc_new/+/6840b22e8e11cb68b493297a5cd757d6eaa0b406/TUNING.md +// These two config options seems useful for frequent allocations and deallocations in +// multi-threaded programs (like we have). +// JE_MALLOC_CONF="background_thread:true,metadata_thp:auto" +// +// Compile Go program with `go build -tags=jemalloc` to enable this. + +type dalloc struct { + t string + sz int +} + +var dallocsMu sync.Mutex +var dallocs map[unsafe.Pointer]*dalloc + +func init() { + // By initializing dallocs, we can start tracking allocations and deallocations via z.Calloc. + dallocs = make(map[unsafe.Pointer]*dalloc) +} + +func Calloc(n int, tag string) []byte { + if n == 0 { + return make([]byte, 0) + } + // We need to be conscious of the Cgo pointer passing rules: + // + // https://golang.org/cmd/cgo/#hdr-Passing_pointers + // + // ... + // Note: the current implementation has a bug. While Go code is permitted + // to write nil or a C pointer (but not a Go pointer) to C memory, the + // current implementation may sometimes cause a runtime error if the + // contents of the C memory appear to be a Go pointer. Therefore, avoid + // passing uninitialized C memory to Go code if the Go code is going to + // store pointer values in it. Zero out the memory in C before passing it + // to Go. + + ptr := C.je_calloc(C.size_t(n), 1) + if ptr == nil { + // NB: throw is like panic, except it guarantees the process will be + // terminated. The call below is exactly what the Go runtime invokes when + // it cannot allocate memory. + throw("out of memory") + } + + uptr := unsafe.Pointer(ptr) + dallocsMu.Lock() + dallocs[uptr] = &dalloc{ + t: tag, + sz: n, + } + dallocsMu.Unlock() + atomic.AddInt64(&numBytes, int64(n)) + // Interpret the C pointer as a pointer to a Go array, then slice. + return (*[MaxArrayLen]byte)(uptr)[:n:n] +} + +// CallocNoRef does the exact same thing as Calloc with jemalloc enabled. +func CallocNoRef(n int, tag string) []byte { + return Calloc(n, tag) +} + +// Free frees the specified slice. +func Free(b []byte) { + if sz := cap(b); sz != 0 { + b = b[:cap(b)] + ptr := unsafe.Pointer(&b[0]) + C.je_free(ptr) + atomic.AddInt64(&numBytes, -int64(sz)) + dallocsMu.Lock() + delete(dallocs, ptr) + dallocsMu.Unlock() + } +} + +func Leaks() string { + if dallocs == nil { + return "Leak detection disabled. Enable with 'leak' build flag." + } + dallocsMu.Lock() + defer dallocsMu.Unlock() + if len(dallocs) == 0 { + return "NO leaks found." + } + m := make(map[string]int) + for _, da := range dallocs { + m[da.t] += da.sz + } + var buf bytes.Buffer + fmt.Fprintf(&buf, "Allocations:\n") + for f, sz := range m { + fmt.Fprintf(&buf, "%s at file: %s\n", humanize.IBytes(uint64(sz)), f) + } + return buf.String() +} + +// ReadMemStats populates stats with JE Malloc statistics. +func ReadMemStats(stats *MemStats) { + if stats == nil { + return + } + // Call an epoch mallclt to refresh the stats data as mentioned in the docs. + // http://jemalloc.net/jemalloc.3.html#epoch + // Note: This epoch mallctl is as expensive as a malloc call. It takes up the + // malloc_mutex_lock. + epoch := 1 + sz := unsafe.Sizeof(&epoch) + C.je_mallctl( + (C.CString)("epoch"), + unsafe.Pointer(&epoch), + (*C.size_t)(unsafe.Pointer(&sz)), + unsafe.Pointer(&epoch), + (C.size_t)(unsafe.Sizeof(epoch))) + stats.Allocated = fetchStat("stats.allocated") + stats.Active = fetchStat("stats.active") + stats.Resident = fetchStat("stats.resident") + stats.Retained = fetchStat("stats.retained") +} + +// fetchStat is used to read a specific attribute from je malloc stats using mallctl. +func fetchStat(s string) uint64 { + var out uint64 + sz := unsafe.Sizeof(&out) + C.je_mallctl( + (C.CString)(s), // Query: eg: stats.allocated, stats.resident, etc. + unsafe.Pointer(&out), // Variable to store the output. + (*C.size_t)(unsafe.Pointer(&sz)), // Size of the output variable. + nil, // Input variable used to set a value. + 0) // Size of the input variable. + return out +} + +func StatsPrint() { + opts := C.CString("mdablxe") + C.je_malloc_stats_print(nil, nil, opts) + C.free(unsafe.Pointer(opts)) +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/calloc_nojemalloc.go b/vendor/github.com/dgraph-io/ristretto/z/calloc_nojemalloc.go new file mode 100644 index 0000000000..93ceedf906 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/calloc_nojemalloc.go @@ -0,0 +1,37 @@ +// Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// +build !jemalloc !cgo + +package z + +import ( + "fmt" +) + +// Provides versions of Calloc, CallocNoRef, etc when jemalloc is not available +// (eg: build without jemalloc tag). + +// Calloc allocates a slice of size n. +func Calloc(n int, tag string) []byte { + return make([]byte, n) +} + +// CallocNoRef will not give you memory back without jemalloc. +func CallocNoRef(n int, tag string) []byte { + // We do the add here just to stay compatible with a corresponding Free call. + return nil +} + +// Free does not do anything in this mode. +func Free(b []byte) {} + +func Leaks() string { return "Leaks: Using Go memory" } +func StatsPrint() { + fmt.Println("Using Go memory") +} + +// ReadMemStats doesn't do anything since all the memory is being managed +// by the Go runtime. +func ReadMemStats(_ *MemStats) { return } diff --git a/vendor/github.com/dgraph-io/ristretto/z/file.go b/vendor/github.com/dgraph-io/ristretto/z/file.go new file mode 100644 index 0000000000..880caf0ad9 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/file.go @@ -0,0 +1,217 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "encoding/binary" + "fmt" + "io" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +// MmapFile represents an mmapd file and includes both the buffer to the data +// and the file descriptor. +type MmapFile struct { + Data []byte + Fd *os.File +} + +var NewFile = errors.New("Create a new file") + +func OpenMmapFileUsing(fd *os.File, sz int, writable bool) (*MmapFile, error) { + filename := fd.Name() + fi, err := fd.Stat() + if err != nil { + return nil, errors.Wrapf(err, "cannot stat file: %s", filename) + } + + var rerr error + fileSize := fi.Size() + if sz > 0 && fileSize == 0 { + // If file is empty, truncate it to sz. + if err := fd.Truncate(int64(sz)); err != nil { + return nil, errors.Wrapf(err, "error while truncation") + } + fileSize = int64(sz) + rerr = NewFile + } + + // fmt.Printf("Mmaping file: %s with writable: %v filesize: %d\n", fd.Name(), writable, fileSize) + buf, err := Mmap(fd, writable, fileSize) // Mmap up to file size. + if err != nil { + return nil, errors.Wrapf(err, "while mmapping %s with size: %d", fd.Name(), fileSize) + } + + if fileSize == 0 { + dir, _ := filepath.Split(filename) + go SyncDir(dir) + } + return &MmapFile{ + Data: buf, + Fd: fd, + }, rerr +} + +// OpenMmapFile opens an existing file or creates a new file. If the file is +// created, it would truncate the file to maxSz. In both cases, it would mmap +// the file to maxSz and returned it. In case the file is created, z.NewFile is +// returned. +func OpenMmapFile(filename string, flag int, maxSz int) (*MmapFile, error) { + // fmt.Printf("opening file %s with flag: %v\n", filename, flag) + fd, err := os.OpenFile(filename, flag, 0666) + if err != nil { + return nil, errors.Wrapf(err, "unable to open: %s", filename) + } + writable := true + if flag == os.O_RDONLY { + writable = false + } + return OpenMmapFileUsing(fd, maxSz, writable) +} + +type mmapReader struct { + Data []byte + offset int +} + +func (mr *mmapReader) Read(buf []byte) (int, error) { + if mr.offset > len(mr.Data) { + return 0, io.EOF + } + n := copy(buf, mr.Data[mr.offset:]) + mr.offset += n + if n < len(buf) { + return n, io.EOF + } + return n, nil +} + +func (m *MmapFile) NewReader(offset int) io.Reader { + return &mmapReader{ + Data: m.Data, + offset: offset, + } +} + +// Bytes returns data starting from offset off of size sz. If there's not enough data, it would +// return nil slice and io.EOF. +func (m *MmapFile) Bytes(off, sz int) ([]byte, error) { + if len(m.Data[off:]) < sz { + return nil, io.EOF + } + return m.Data[off : off+sz], nil +} + +// Slice returns the slice at the given offset. +func (m *MmapFile) Slice(offset int) []byte { + sz := binary.BigEndian.Uint32(m.Data[offset:]) + start := offset + 4 + next := start + int(sz) + if next > len(m.Data) { + return []byte{} + } + res := m.Data[start:next] + return res +} + +// AllocateSlice allocates a slice of the given size at the given offset. +func (m *MmapFile) AllocateSlice(sz, offset int) ([]byte, int, error) { + start := offset + 4 + + // If the file is too small, double its size or increase it by 1GB, whichever is smaller. + if start+sz > len(m.Data) { + const oneGB = 1 << 30 + growBy := len(m.Data) + if growBy > oneGB { + growBy = oneGB + } + if growBy < sz+4 { + growBy = sz + 4 + } + if err := m.Truncate(int64(len(m.Data) + growBy)); err != nil { + return nil, 0, err + } + } + + binary.BigEndian.PutUint32(m.Data[offset:], uint32(sz)) + return m.Data[start : start+sz], start + sz, nil +} + +func (m *MmapFile) Sync() error { + if m == nil { + return nil + } + return Msync(m.Data) +} + +func (m *MmapFile) Delete() error { + // Badger can set the m.Data directly, without setting any Fd. In that case, this should be a + // NOOP. + if m.Fd == nil { + return nil + } + + if err := Munmap(m.Data); err != nil { + return fmt.Errorf("while munmap file: %s, error: %v\n", m.Fd.Name(), err) + } + m.Data = nil + if err := m.Fd.Truncate(0); err != nil { + return fmt.Errorf("while truncate file: %s, error: %v\n", m.Fd.Name(), err) + } + if err := m.Fd.Close(); err != nil { + return fmt.Errorf("while close file: %s, error: %v\n", m.Fd.Name(), err) + } + return os.Remove(m.Fd.Name()) +} + +// Close would close the file. It would also truncate the file if maxSz >= 0. +func (m *MmapFile) Close(maxSz int64) error { + // Badger can set the m.Data directly, without setting any Fd. In that case, this should be a + // NOOP. + if m.Fd == nil { + return nil + } + if err := m.Sync(); err != nil { + return fmt.Errorf("while sync file: %s, error: %v\n", m.Fd.Name(), err) + } + if err := Munmap(m.Data); err != nil { + return fmt.Errorf("while munmap file: %s, error: %v\n", m.Fd.Name(), err) + } + if maxSz >= 0 { + if err := m.Fd.Truncate(maxSz); err != nil { + return fmt.Errorf("while truncate file: %s, error: %v\n", m.Fd.Name(), err) + } + } + return m.Fd.Close() +} + +func SyncDir(dir string) error { + df, err := os.Open(dir) + if err != nil { + return errors.Wrapf(err, "while opening %s", dir) + } + if err := df.Sync(); err != nil { + return errors.Wrapf(err, "while syncing %s", dir) + } + if err := df.Close(); err != nil { + return errors.Wrapf(err, "while closing %s", dir) + } + return nil +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/file_default.go b/vendor/github.com/dgraph-io/ristretto/z/file_default.go new file mode 100644 index 0000000000..d9c0db43e7 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/file_default.go @@ -0,0 +1,39 @@ +// +build !linux + +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import "fmt" + +// Truncate would truncate the mmapped file to the given size. On Linux, we truncate +// the underlying file and then call mremap, but on other systems, we unmap first, +// then truncate, then re-map. +func (m *MmapFile) Truncate(maxSz int64) error { + if err := m.Sync(); err != nil { + return fmt.Errorf("while sync file: %s, error: %v\n", m.Fd.Name(), err) + } + if err := Munmap(m.Data); err != nil { + return fmt.Errorf("while munmap file: %s, error: %v\n", m.Fd.Name(), err) + } + if err := m.Fd.Truncate(maxSz); err != nil { + return fmt.Errorf("while truncate file: %s, error: %v\n", m.Fd.Name(), err) + } + var err error + m.Data, err = Mmap(m.Fd, true, maxSz) // Mmap up to max size. + return err +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/file_linux.go b/vendor/github.com/dgraph-io/ristretto/z/file_linux.go new file mode 100644 index 0000000000..7f670bd2cc --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/file_linux.go @@ -0,0 +1,37 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "fmt" +) + +// Truncate would truncate the mmapped file to the given size. On Linux, we truncate +// the underlying file and then call mremap, but on other systems, we unmap first, +// then truncate, then re-map. +func (m *MmapFile) Truncate(maxSz int64) error { + if err := m.Sync(); err != nil { + return fmt.Errorf("while sync file: %s, error: %v\n", m.Fd.Name(), err) + } + if err := m.Fd.Truncate(maxSz); err != nil { + return fmt.Errorf("while truncate file: %s, error: %v\n", m.Fd.Name(), err) + } + + var err error + m.Data, err = mremap(m.Data, int(maxSz)) // Mmap up to max size. + return err +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/flags.go b/vendor/github.com/dgraph-io/ristretto/z/flags.go new file mode 100644 index 0000000000..a55c474ab2 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/flags.go @@ -0,0 +1,311 @@ +package z + +import ( + "fmt" + "os" + "os/user" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + "github.com/golang/glog" + "github.com/pkg/errors" +) + +// SuperFlagHelp makes it really easy to generate command line `--help` output for a SuperFlag. For +// example: +// +// const flagDefaults = `enabled=true; path=some/path;` +// +// var help string = z.NewSuperFlagHelp(flagDefaults). +// Flag("enabled", "Turns on ."). +// Flag("path", "The path to ."). +// Flag("another", "Not present in defaults, but still included."). +// String() +// +// The `help` string would then contain: +// +// enabled=true; Turns on . +// path=some/path; The path to . +// another=; Not present in defaults, but still included. +// +// All flags are sorted alphabetically for consistent `--help` output. Flags with default values are +// placed at the top, and everything else goes under. +type SuperFlagHelp struct { + head string + defaults *SuperFlag + flags map[string]string +} + +func NewSuperFlagHelp(defaults string) *SuperFlagHelp { + return &SuperFlagHelp{ + defaults: NewSuperFlag(defaults), + flags: make(map[string]string, 0), + } +} + +func (h *SuperFlagHelp) Head(head string) *SuperFlagHelp { + h.head = head + return h +} + +func (h *SuperFlagHelp) Flag(name, description string) *SuperFlagHelp { + h.flags[name] = description + return h +} + +func (h *SuperFlagHelp) String() string { + defaultLines := make([]string, 0) + otherLines := make([]string, 0) + for name, help := range h.flags { + val, found := h.defaults.m[name] + line := fmt.Sprintf(" %s=%s; %s\n", name, val, help) + if found { + defaultLines = append(defaultLines, line) + } else { + otherLines = append(otherLines, line) + } + } + sort.Strings(defaultLines) + sort.Strings(otherLines) + dls := strings.Join(defaultLines, "") + ols := strings.Join(otherLines, "") + if len(h.defaults.m) == 0 && len(ols) == 0 { + // remove last newline + dls = dls[:len(dls)-1] + } + // remove last newline + if len(h.defaults.m) == 0 && len(ols) > 1 { + ols = ols[:len(ols)-1] + } + return h.head + "\n" + dls + ols +} + +func parseFlag(flag string) (map[string]string, error) { + kvm := make(map[string]string) + for _, kv := range strings.Split(flag, ";") { + if strings.TrimSpace(kv) == "" { + continue + } + // For a non-empty separator, 0 < len(splits) ≤ 2. + splits := strings.SplitN(kv, "=", 2) + k := strings.TrimSpace(splits[0]) + if len(splits) < 2 { + return nil, fmt.Errorf("superflag: missing value for '%s' in flag: %s", k, flag) + } + k = strings.ToLower(k) + k = strings.ReplaceAll(k, "_", "-") + kvm[k] = strings.TrimSpace(splits[1]) + } + return kvm, nil +} + +type SuperFlag struct { + m map[string]string +} + +func NewSuperFlag(flag string) *SuperFlag { + sf, err := newSuperFlagImpl(flag) + if err != nil { + glog.Fatal(err) + } + return sf +} + +func newSuperFlagImpl(flag string) (*SuperFlag, error) { + m, err := parseFlag(flag) + if err != nil { + return nil, err + } + return &SuperFlag{m}, nil +} + +func (sf *SuperFlag) String() string { + if sf == nil { + return "" + } + kvs := make([]string, 0, len(sf.m)) + for k, v := range sf.m { + kvs = append(kvs, fmt.Sprintf("%s=%s", k, v)) + } + return strings.Join(kvs, "; ") +} + +func (sf *SuperFlag) MergeAndCheckDefault(flag string) *SuperFlag { + sf, err := sf.mergeAndCheckDefaultImpl(flag) + if err != nil { + glog.Fatal(err) + } + return sf +} + +func (sf *SuperFlag) mergeAndCheckDefaultImpl(flag string) (*SuperFlag, error) { + if sf == nil { + m, err := parseFlag(flag) + if err != nil { + return nil, err + } + return &SuperFlag{m}, nil + } + + src, err := parseFlag(flag) + if err != nil { + return nil, err + } + + numKeys := len(sf.m) + for k := range src { + if _, ok := sf.m[k]; ok { + numKeys-- + } + } + if numKeys != 0 { + return nil, fmt.Errorf("superflag: found invalid options in flag: %s.\nvalid options: %v", sf, flag) + } + for k, v := range src { + if _, ok := sf.m[k]; !ok { + sf.m[k] = v + } + } + return sf, nil +} + +func (sf *SuperFlag) Has(opt string) bool { + val := sf.GetString(opt) + return val != "" +} + +func (sf *SuperFlag) GetDuration(opt string) time.Duration { + val := sf.GetString(opt) + if val == "" { + return time.Duration(0) + } + if strings.Contains(val, "d") { + val = strings.Replace(val, "d", "", 1) + days, err := strconv.ParseUint(val, 0, 64) + if err != nil { + return time.Duration(0) + } + return time.Hour * 24 * time.Duration(days) + } + d, err := time.ParseDuration(val) + if err != nil { + return time.Duration(0) + } + return d +} + +func (sf *SuperFlag) GetBool(opt string) bool { + val := sf.GetString(opt) + if val == "" { + return false + } + b, err := strconv.ParseBool(val) + if err != nil { + err = errors.Wrapf(err, + "Unable to parse %s as bool for key: %s. Options: %s\n", + val, opt, sf) + glog.Fatalf("%+v", err) + } + return b +} + +func (sf *SuperFlag) GetFloat64(opt string) float64 { + val := sf.GetString(opt) + if val == "" { + return 0 + } + f, err := strconv.ParseFloat(val, 64) + if err != nil { + err = errors.Wrapf(err, + "Unable to parse %s as float64 for key: %s. Options: %s\n", + val, opt, sf) + glog.Fatalf("%+v", err) + } + return f +} + +func (sf *SuperFlag) GetInt64(opt string) int64 { + val := sf.GetString(opt) + if val == "" { + return 0 + } + i, err := strconv.ParseInt(val, 0, 64) + if err != nil { + err = errors.Wrapf(err, + "Unable to parse %s as int64 for key: %s. Options: %s\n", + val, opt, sf) + glog.Fatalf("%+v", err) + } + return i +} + +func (sf *SuperFlag) GetUint64(opt string) uint64 { + val := sf.GetString(opt) + if val == "" { + return 0 + } + u, err := strconv.ParseUint(val, 0, 64) + if err != nil { + err = errors.Wrapf(err, + "Unable to parse %s as uint64 for key: %s. Options: %s\n", + val, opt, sf) + glog.Fatalf("%+v", err) + } + return u +} + +func (sf *SuperFlag) GetUint32(opt string) uint32 { + val := sf.GetString(opt) + if val == "" { + return 0 + } + u, err := strconv.ParseUint(val, 0, 32) + if err != nil { + err = errors.Wrapf(err, + "Unable to parse %s as uint32 for key: %s. Options: %s\n", + val, opt, sf) + glog.Fatalf("%+v", err) + } + return uint32(u) +} + +func (sf *SuperFlag) GetString(opt string) string { + if sf == nil { + return "" + } + return sf.m[opt] +} + +func (sf *SuperFlag) GetPath(opt string) string { + p := sf.GetString(opt) + path, err := expandPath(p) + if err != nil { + glog.Fatalf("Failed to get path: %+v", err) + } + return path +} + +// expandPath expands the paths containing ~ to /home/user. It also computes the absolute path +// from the relative paths. For example: ~/abc/../cef will be transformed to /home/user/cef. +func expandPath(path string) (string, error) { + if len(path) == 0 { + return "", nil + } + if path[0] == '~' && (len(path) == 1 || os.IsPathSeparator(path[1])) { + usr, err := user.Current() + if err != nil { + return "", errors.Wrap(err, "Failed to get the home directory of the user") + } + path = filepath.Join(usr.HomeDir, path[1:]) + } + + var err error + path, err = filepath.Abs(path) + if err != nil { + return "", errors.Wrap(err, "Failed to generate absolute path") + } + return path, nil +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/histogram.go b/vendor/github.com/dgraph-io/ristretto/z/histogram.go new file mode 100644 index 0000000000..4eb0c4f6c9 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/histogram.go @@ -0,0 +1,205 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "fmt" + "math" + "strings" + + "github.com/dustin/go-humanize" +) + +// Creates bounds for an histogram. The bounds are powers of two of the form +// [2^min_exponent, ..., 2^max_exponent]. +func HistogramBounds(minExponent, maxExponent uint32) []float64 { + var bounds []float64 + for i := minExponent; i <= maxExponent; i++ { + bounds = append(bounds, float64(int(1)< 4) + bounds := make([]float64, num) + bounds[0] = 1 + bounds[1] = 2 + for i := 2; i < num; i++ { + bounds[i] = bounds[i-1] + bounds[i-2] + } + return bounds +} + +// HistogramData stores the information needed to represent the sizes of the keys and values +// as a histogram. +type HistogramData struct { + Bounds []float64 + Count int64 + CountPerBucket []int64 + Min int64 + Max int64 + Sum int64 +} + +// NewHistogramData returns a new instance of HistogramData with properly initialized fields. +func NewHistogramData(bounds []float64) *HistogramData { + return &HistogramData{ + Bounds: bounds, + CountPerBucket: make([]int64, len(bounds)+1), + Max: 0, + Min: math.MaxInt64, + } +} + +func (histogram *HistogramData) Copy() *HistogramData { + if histogram == nil { + return nil + } + return &HistogramData{ + Bounds: append([]float64{}, histogram.Bounds...), + CountPerBucket: append([]int64{}, histogram.CountPerBucket...), + Count: histogram.Count, + Min: histogram.Min, + Max: histogram.Max, + Sum: histogram.Sum, + } +} + +// Update changes the Min and Max fields if value is less than or greater than the current values. +func (histogram *HistogramData) Update(value int64) { + if histogram == nil { + return + } + if value > histogram.Max { + histogram.Max = value + } + if value < histogram.Min { + histogram.Min = value + } + + histogram.Sum += value + histogram.Count++ + + for index := 0; index <= len(histogram.Bounds); index++ { + // Allocate value in the last buckets if we reached the end of the Bounds array. + if index == len(histogram.Bounds) { + histogram.CountPerBucket[index]++ + break + } + + if value < int64(histogram.Bounds[index]) { + histogram.CountPerBucket[index]++ + break + } + } +} + +// Mean returns the mean value for the histogram. +func (histogram *HistogramData) Mean() float64 { + if histogram.Count == 0 { + return 0 + } + return float64(histogram.Sum) / float64(histogram.Count) +} + +// String converts the histogram data into human-readable string. +func (histogram *HistogramData) String() string { + if histogram == nil { + return "" + } + var b strings.Builder + + b.WriteString("\n -- Histogram: \n") + b.WriteString(fmt.Sprintf("Min value: %d \n", histogram.Min)) + b.WriteString(fmt.Sprintf("Max value: %d \n", histogram.Max)) + b.WriteString(fmt.Sprintf("Count: %d \n", histogram.Count)) + b.WriteString(fmt.Sprintf("50p: %.2f \n", histogram.Percentile(0.5))) + b.WriteString(fmt.Sprintf("75p: %.2f \n", histogram.Percentile(0.75))) + b.WriteString(fmt.Sprintf("90p: %.2f \n", histogram.Percentile(0.90))) + + numBounds := len(histogram.Bounds) + var cum float64 + for index, count := range histogram.CountPerBucket { + if count == 0 { + continue + } + + // The last bucket represents the bucket that contains the range from + // the last bound up to infinity so it's processed differently than the + // other buckets. + if index == len(histogram.CountPerBucket)-1 { + lowerBound := uint64(histogram.Bounds[numBounds-1]) + page := float64(count*100) / float64(histogram.Count) + cum += page + b.WriteString(fmt.Sprintf("[%s, %s) %d %.2f%% %.2f%%\n", + humanize.IBytes(lowerBound), "infinity", count, page, cum)) + continue + } + + upperBound := uint64(histogram.Bounds[index]) + lowerBound := uint64(0) + if index > 0 { + lowerBound = uint64(histogram.Bounds[index-1]) + } + + page := float64(count*100) / float64(histogram.Count) + cum += page + b.WriteString(fmt.Sprintf("[%d, %d) %d %.2f%% %.2f%%\n", + lowerBound, upperBound, count, page, cum)) + } + b.WriteString(" --\n") + return b.String() +} + +// Percentile returns the percentile value for the histogram. +// value of p should be between [0.0-1.0] +func (histogram *HistogramData) Percentile(p float64) float64 { + if histogram == nil { + return 0 + } + + if histogram.Count == 0 { + // if no data return the minimum range + return histogram.Bounds[0] + } + pval := int64(float64(histogram.Count) * p) + for i, v := range histogram.CountPerBucket { + pval = pval - v + if pval <= 0 { + if i == len(histogram.Bounds) { + break + } + return histogram.Bounds[i] + } + } + // default return should be the max range + return histogram.Bounds[len(histogram.Bounds)-1] +} + +// Clear reset the histogram. Helpful in situations where we need to reset the metrics +func (histogram *HistogramData) Clear() { + if histogram == nil { + return + } + + histogram.Count = 0 + histogram.CountPerBucket = make([]int64, len(histogram.Bounds)+1) + histogram.Sum = 0 + histogram.Max = 0 + histogram.Min = math.MaxInt64 +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/mmap.go b/vendor/github.com/dgraph-io/ristretto/z/mmap.go new file mode 100644 index 0000000000..9b02510003 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/mmap.go @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "os" +) + +// Mmap uses the mmap system call to memory-map a file. If writable is true, +// memory protection of the pages is set so that they may be written to as well. +func Mmap(fd *os.File, writable bool, size int64) ([]byte, error) { + return mmap(fd, writable, size) +} + +// Munmap unmaps a previously mapped slice. +func Munmap(b []byte) error { + return munmap(b) +} + +// Madvise uses the madvise system call to give advise about the use of memory +// when using a slice that is memory-mapped to a file. Set the readahead flag to +// false if page references are expected in random order. +func Madvise(b []byte, readahead bool) error { + return madvise(b, readahead) +} + +// Msync would call sync on the mmapped data. +func Msync(b []byte) error { + return msync(b) +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/mmap_darwin.go b/vendor/github.com/dgraph-io/ristretto/z/mmap_darwin.go new file mode 100644 index 0000000000..4d6d74f193 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/mmap_darwin.go @@ -0,0 +1,59 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "os" + "syscall" + "unsafe" + + "golang.org/x/sys/unix" +) + +// Mmap uses the mmap system call to memory-map a file. If writable is true, +// memory protection of the pages is set so that they may be written to as well. +func mmap(fd *os.File, writable bool, size int64) ([]byte, error) { + mtype := unix.PROT_READ + if writable { + mtype |= unix.PROT_WRITE + } + return unix.Mmap(int(fd.Fd()), 0, int(size), mtype, unix.MAP_SHARED) +} + +// Munmap unmaps a previously mapped slice. +func munmap(b []byte) error { + return unix.Munmap(b) +} + +// This is required because the unix package does not support the madvise system call on OS X. +func madvise(b []byte, readahead bool) error { + advice := unix.MADV_NORMAL + if !readahead { + advice = unix.MADV_RANDOM + } + + _, _, e1 := syscall.Syscall(syscall.SYS_MADVISE, uintptr(unsafe.Pointer(&b[0])), + uintptr(len(b)), uintptr(advice)) + if e1 != 0 { + return e1 + } + return nil +} + +func msync(b []byte) error { + return unix.Msync(b, unix.MS_SYNC) +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/mmap_linux.go b/vendor/github.com/dgraph-io/ristretto/z/mmap_linux.go new file mode 100644 index 0000000000..9cc3497a16 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/mmap_linux.go @@ -0,0 +1,101 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "fmt" + "os" + "reflect" + "unsafe" + + "golang.org/x/sys/unix" +) + +// mmap uses the mmap system call to memory-map a file. If writable is true, +// memory protection of the pages is set so that they may be written to as well. +func mmap(fd *os.File, writable bool, size int64) ([]byte, error) { + mtype := unix.PROT_READ + if writable { + mtype |= unix.PROT_WRITE + } + return unix.Mmap(int(fd.Fd()), 0, int(size), mtype, unix.MAP_SHARED) +} + +// mremap is a Linux-specific system call to remap pages in memory. This can be used in place of munmap + mmap. +func mremap(data []byte, size int) ([]byte, error) { + // taken from + const MREMAP_MAYMOVE = 0x1 + + header := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + mmapAddr, mmapSize, errno := unix.Syscall6( + unix.SYS_MREMAP, + header.Data, + uintptr(header.Len), + uintptr(size), + uintptr(MREMAP_MAYMOVE), + 0, + 0, + ) + if errno != 0 { + return nil, errno + } + if mmapSize != uintptr(size) { + return nil, fmt.Errorf("mremap size mismatch: requested: %d got: %d", size, mmapSize) + } + + header.Data = mmapAddr + header.Cap = size + header.Len = size + return data, nil +} + +// munmap unmaps a previously mapped slice. +// +// unix.Munmap maintains an internal list of mmapped addresses, and only calls munmap +// if the address is present in that list. If we use mremap, this list is not updated. +// To bypass this, we call munmap ourselves. +func munmap(data []byte) error { + if len(data) == 0 || len(data) != cap(data) { + return unix.EINVAL + } + _, _, errno := unix.Syscall( + unix.SYS_MUNMAP, + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + 0, + ) + if errno != 0 { + return errno + } + return nil +} + +// madvise uses the madvise system call to give advise about the use of memory +// when using a slice that is memory-mapped to a file. Set the readahead flag to +// false if page references are expected in random order. +func madvise(b []byte, readahead bool) error { + flags := unix.MADV_NORMAL + if !readahead { + flags = unix.MADV_RANDOM + } + return unix.Madvise(b, flags) +} + +// msync writes any modified data to persistent storage. +func msync(b []byte) error { + return unix.Msync(b, unix.MS_SYNC) +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/mmap_plan9.go b/vendor/github.com/dgraph-io/ristretto/z/mmap_plan9.go new file mode 100644 index 0000000000..f30729654f --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/mmap_plan9.go @@ -0,0 +1,44 @@ +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "os" + "syscall" +) + +// Mmap uses the mmap system call to memory-map a file. If writable is true, +// memory protection of the pages is set so that they may be written to as well. +func mmap(fd *os.File, writable bool, size int64) ([]byte, error) { + return nil, syscall.EPLAN9 +} + +// Munmap unmaps a previously mapped slice. +func munmap(b []byte) error { + return syscall.EPLAN9 +} + +// Madvise uses the madvise system call to give advise about the use of memory +// when using a slice that is memory-mapped to a file. Set the readahead flag to +// false if page references are expected in random order. +func madvise(b []byte, readahead bool) error { + return syscall.EPLAN9 +} + +func msync(b []byte) error { + return syscall.EPLAN9 +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/mmap_unix.go b/vendor/github.com/dgraph-io/ristretto/z/mmap_unix.go new file mode 100644 index 0000000000..e8b2699cf9 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/mmap_unix.go @@ -0,0 +1,55 @@ +// +build !windows,!darwin,!plan9,!linux + +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "os" + + "golang.org/x/sys/unix" +) + +// Mmap uses the mmap system call to memory-map a file. If writable is true, +// memory protection of the pages is set so that they may be written to as well. +func mmap(fd *os.File, writable bool, size int64) ([]byte, error) { + mtype := unix.PROT_READ + if writable { + mtype |= unix.PROT_WRITE + } + return unix.Mmap(int(fd.Fd()), 0, int(size), mtype, unix.MAP_SHARED) +} + +// Munmap unmaps a previously mapped slice. +func munmap(b []byte) error { + return unix.Munmap(b) +} + +// Madvise uses the madvise system call to give advise about the use of memory +// when using a slice that is memory-mapped to a file. Set the readahead flag to +// false if page references are expected in random order. +func madvise(b []byte, readahead bool) error { + flags := unix.MADV_NORMAL + if !readahead { + flags = unix.MADV_RANDOM + } + return unix.Madvise(b, flags) +} + +func msync(b []byte) error { + return unix.Msync(b, unix.MS_SYNC) +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/mmap_windows.go b/vendor/github.com/dgraph-io/ristretto/z/mmap_windows.go new file mode 100644 index 0000000000..171176e9fe --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/mmap_windows.go @@ -0,0 +1,96 @@ +// +build windows + +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +func mmap(fd *os.File, write bool, size int64) ([]byte, error) { + protect := syscall.PAGE_READONLY + access := syscall.FILE_MAP_READ + + if write { + protect = syscall.PAGE_READWRITE + access = syscall.FILE_MAP_WRITE + } + fi, err := fd.Stat() + if err != nil { + return nil, err + } + + // In windows, we cannot mmap a file more than it's actual size. + // So truncate the file to the size of the mmap. + if fi.Size() < size { + if err := fd.Truncate(size); err != nil { + return nil, fmt.Errorf("truncate: %s", err) + } + } + + // Open a file mapping handle. + sizelo := uint32(size >> 32) + sizehi := uint32(size) & 0xffffffff + + handler, err := syscall.CreateFileMapping(syscall.Handle(fd.Fd()), nil, + uint32(protect), sizelo, sizehi, nil) + if err != nil { + return nil, os.NewSyscallError("CreateFileMapping", err) + } + + // Create the memory map. + addr, err := syscall.MapViewOfFile(handler, uint32(access), 0, 0, uintptr(size)) + if addr == 0 { + return nil, os.NewSyscallError("MapViewOfFile", err) + } + + // Close mapping handle. + if err := syscall.CloseHandle(syscall.Handle(handler)); err != nil { + return nil, os.NewSyscallError("CloseHandle", err) + } + + // Slice memory layout + // Copied this snippet from golang/sys package + var sl = struct { + addr uintptr + len int + cap int + }{addr, int(size), int(size)} + + // Use unsafe to turn sl into a []byte. + data := *(*[]byte)(unsafe.Pointer(&sl)) + + return data, nil +} + +func munmap(b []byte) error { + return syscall.UnmapViewOfFile(uintptr(unsafe.Pointer(&b[0]))) +} + +func madvise(b []byte, readahead bool) error { + // Do Nothing. We don’t care about this setting on Windows + return nil +} + +func msync(b []byte) error { + // TODO: Figure out how to do msync on Windows. + return nil +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/rtutil.go b/vendor/github.com/dgraph-io/ristretto/z/rtutil.go new file mode 100644 index 0000000000..8f317c80d3 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/rtutil.go @@ -0,0 +1,75 @@ +// MIT License + +// Copyright (c) 2019 Ewan Chou + +// 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. + +package z + +import ( + "unsafe" +) + +// NanoTime returns the current time in nanoseconds from a monotonic clock. +//go:linkname NanoTime runtime.nanotime +func NanoTime() int64 + +// CPUTicks is a faster alternative to NanoTime to measure time duration. +//go:linkname CPUTicks runtime.cputicks +func CPUTicks() int64 + +type stringStruct struct { + str unsafe.Pointer + len int +} + +//go:noescape +//go:linkname memhash runtime.memhash +func memhash(p unsafe.Pointer, h, s uintptr) uintptr + +// MemHash is the hash function used by go map, it utilizes available hardware instructions(behaves +// as aeshash if aes instruction is available). +// NOTE: The hash seed changes for every process. So, this cannot be used as a persistent hash. +func MemHash(data []byte) uint64 { + ss := (*stringStruct)(unsafe.Pointer(&data)) + return uint64(memhash(ss.str, 0, uintptr(ss.len))) +} + +// MemHashString is the hash function used by go map, it utilizes available hardware instructions +// (behaves as aeshash if aes instruction is available). +// NOTE: The hash seed changes for every process. So, this cannot be used as a persistent hash. +func MemHashString(str string) uint64 { + ss := (*stringStruct)(unsafe.Pointer(&str)) + return uint64(memhash(ss.str, 0, uintptr(ss.len))) +} + +// FastRand is a fast thread local random function. +//go:linkname FastRand runtime.fastrand +func FastRand() uint32 + +//go:linkname memclrNoHeapPointers runtime.memclrNoHeapPointers +func memclrNoHeapPointers(p unsafe.Pointer, n uintptr) + +func Memclr(b []byte) { + if len(b) == 0 { + return + } + p := unsafe.Pointer(&b[0]) + memclrNoHeapPointers(p, uintptr(len(b))) +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/rtutil.s b/vendor/github.com/dgraph-io/ristretto/z/rtutil.s new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vendor/github.com/dgraph-io/ristretto/z/simd/baseline.go b/vendor/github.com/dgraph-io/ristretto/z/simd/baseline.go new file mode 100644 index 0000000000..967e3a307e --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/simd/baseline.go @@ -0,0 +1,127 @@ +package simd + +import ( + "fmt" + "runtime" + "sort" + "sync" +) + +// Search finds the key using the naive way +func Naive(xs []uint64, k uint64) int16 { + var i int + for i = 0; i < len(xs); i += 2 { + x := xs[i] + if x >= k { + return int16(i / 2) + } + } + return int16(i / 2) +} + +func Clever(xs []uint64, k uint64) int16 { + if len(xs) < 8 { + return Naive(xs, k) + } + var twos, pk [4]uint64 + pk[0] = k + pk[1] = k + pk[2] = k + pk[3] = k + for i := 0; i < len(xs); i += 8 { + twos[0] = xs[i] + twos[1] = xs[i+2] + twos[2] = xs[i+4] + twos[3] = xs[i+6] + if twos[0] >= pk[0] { + return int16(i / 2) + } + if twos[1] >= pk[1] { + return int16((i + 2) / 2) + } + if twos[2] >= pk[2] { + return int16((i + 4) / 2) + } + if twos[3] >= pk[3] { + return int16((i + 6) / 2) + } + + } + return int16(len(xs) / 2) +} + +func Parallel(xs []uint64, k uint64) int16 { + cpus := runtime.NumCPU() + if cpus%2 != 0 { + panic(fmt.Sprintf("odd number of CPUs %v", cpus)) + } + sz := len(xs)/cpus + 1 + var wg sync.WaitGroup + retChan := make(chan int16, cpus) + for i := 0; i < len(xs); i += sz { + end := i + sz + if end >= len(xs) { + end = len(xs) + } + chunk := xs[i:end] + wg.Add(1) + go func(hd int16, xs []uint64, k uint64, wg *sync.WaitGroup, ch chan int16) { + for i := 0; i < len(xs); i += 2 { + if xs[i] >= k { + ch <- (int16(i) + hd) / 2 + break + } + } + wg.Done() + }(int16(i), chunk, k, &wg, retChan) + } + wg.Wait() + close(retChan) + var min int16 = (1 << 15) - 1 + for i := range retChan { + if i < min { + min = i + } + } + if min == (1<<15)-1 { + return int16(len(xs) / 2) + } + return min +} + +func Binary(keys []uint64, key uint64) int16 { + return int16(sort.Search(len(keys), func(i int) bool { + if i*2 >= len(keys) { + return true + } + return keys[i*2] >= key + })) +} + +func cmp2_native(twos, pk [2]uint64) int16 { + if twos[0] == pk[0] { + return 0 + } + if twos[1] == pk[1] { + return 1 + } + return 2 +} + +func cmp4_native(fours, pk [4]uint64) int16 { + for i := range fours { + if fours[i] >= pk[i] { + return int16(i) + } + } + return 4 +} + +func cmp8_native(a [8]uint64, pk [4]uint64) int16 { + for i := range a { + if a[i] >= pk[0] { + return int16(i) + } + } + return 8 +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/simd/search.go b/vendor/github.com/dgraph-io/ristretto/z/simd/search.go new file mode 100644 index 0000000000..0d001ee0c4 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/simd/search.go @@ -0,0 +1,51 @@ +// +build !amd64 + +/* + * Copyright 2020 Dgraph Labs, Inc. and Contributors + * + * 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 simd + +// Search uses the Clever search to find the correct key. +func Search(xs []uint64, k uint64) int16 { + if len(xs) < 8 { + return Naive(xs, k) + } + var twos, pk [4]uint64 + pk[0] = k + pk[1] = k + pk[2] = k + pk[3] = k + for i := 0; i < len(xs); i += 8 { + twos[0] = xs[i] + twos[1] = xs[i+2] + twos[2] = xs[i+4] + twos[3] = xs[i+6] + if twos[0] >= pk[0] { + return int16(i / 2) + } + if twos[1] >= pk[1] { + return int16((i + 2) / 2) + } + if twos[2] >= pk[2] { + return int16((i + 4) / 2) + } + if twos[3] >= pk[3] { + return int16((i + 6) / 2) + } + + } + return int16(len(xs) / 2) +} diff --git a/vendor/github.com/dgraph-io/ristretto/z/simd/search_amd64.s b/vendor/github.com/dgraph-io/ristretto/z/simd/search_amd64.s new file mode 100644 index 0000000000..150c846647 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/simd/search_amd64.s @@ -0,0 +1,60 @@ +// Code generated by command: go run asm2.go -out search_amd64.s -stubs stub_search_amd64.go. DO NOT EDIT. + +#include "textflag.h" + +// func Search(xs []uint64, k uint64) int16 +TEXT ·Search(SB), NOSPLIT, $0-34 + MOVQ xs_base+0(FP), AX + MOVQ xs_len+8(FP), CX + MOVQ k+24(FP), DX + + // Save n + MOVQ CX, BX + + // Initialize idx register to zero. + XORL BP, BP + +loop: + // Unroll1 + CMPQ (AX)(BP*8), DX + JAE Found + + // Unroll2 + CMPQ 16(AX)(BP*8), DX + JAE Found2 + + // Unroll3 + CMPQ 32(AX)(BP*8), DX + JAE Found3 + + // Unroll4 + CMPQ 48(AX)(BP*8), DX + JAE Found4 + + // plus8 + ADDQ $0x08, BP + CMPQ BP, CX + JB loop + JMP NotFound + +Found2: + ADDL $0x02, BP + JMP Found + +Found3: + ADDL $0x04, BP + JMP Found + +Found4: + ADDL $0x06, BP + +Found: + MOVL BP, BX + +NotFound: + MOVL BX, BP + SHRL $0x1f, BP + ADDL BX, BP + SHRL $0x01, BP + MOVL BP, ret+32(FP) + RET diff --git a/vendor/github.com/dgraph-io/ristretto/z/simd/stub_search_amd64.go b/vendor/github.com/dgraph-io/ristretto/z/simd/stub_search_amd64.go new file mode 100644 index 0000000000..0821d38a77 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/simd/stub_search_amd64.go @@ -0,0 +1,6 @@ +// Code generated by command: go run asm2.go -out search_amd64.s -stubs stub_search_amd64.go. DO NOT EDIT. + +package simd + +// Search finds the first idx for which xs[idx] >= k in xs. +func Search(xs []uint64, k uint64) int16 diff --git a/vendor/github.com/dgraph-io/ristretto/z/z.go b/vendor/github.com/dgraph-io/ristretto/z/z.go new file mode 100644 index 0000000000..97455586a1 --- /dev/null +++ b/vendor/github.com/dgraph-io/ristretto/z/z.go @@ -0,0 +1,151 @@ +/* + * Copyright 2019 Dgraph Labs, Inc. and Contributors + * + * 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 z + +import ( + "context" + "sync" + + "github.com/cespare/xxhash/v2" +) + +// TODO: Figure out a way to re-use memhash for the second uint64 hash, we +// already know that appending bytes isn't reliable for generating a +// second hash (see Ristretto PR #88). +// +// We also know that while the Go runtime has a runtime memhash128 +// function, it's not possible to use it to generate [2]uint64 or +// anything resembling a 128bit hash, even though that's exactly what +// we need in this situation. +func KeyToHash(key interface{}) (uint64, uint64) { + if key == nil { + return 0, 0 + } + switch k := key.(type) { + case uint64: + return k, 0 + case string: + return MemHashString(k), xxhash.Sum64String(k) + case []byte: + return MemHash(k), xxhash.Sum64(k) + case byte: + return uint64(k), 0 + case int: + return uint64(k), 0 + case int32: + return uint64(k), 0 + case uint32: + return uint64(k), 0 + case int64: + return uint64(k), 0 + default: + panic("Key type not supported") + } +} + +var ( + dummyCloserChan <-chan struct{} + tmpDir string +) + +// Closer holds the two things we need to close a goroutine and wait for it to +// finish: a chan to tell the goroutine to shut down, and a WaitGroup with +// which to wait for it to finish shutting down. +type Closer struct { + waiting sync.WaitGroup + + ctx context.Context + cancel context.CancelFunc +} + +// SetTmpDir sets the temporary directory for the temporary buffers. +func SetTmpDir(dir string) { + tmpDir = dir +} + +// NewCloser constructs a new Closer, with an initial count on the WaitGroup. +func NewCloser(initial int) *Closer { + ret := &Closer{} + ret.ctx, ret.cancel = context.WithCancel(context.Background()) + ret.waiting.Add(initial) + return ret +} + +// AddRunning Add()'s delta to the WaitGroup. +func (lc *Closer) AddRunning(delta int) { + lc.waiting.Add(delta) +} + +// Ctx can be used to get a context, which would automatically get cancelled when Signal is called. +func (lc *Closer) Ctx() context.Context { + if lc == nil { + return context.Background() + } + return lc.ctx +} + +// Signal signals the HasBeenClosed signal. +func (lc *Closer) Signal() { + // Todo(ibrahim): Change Signal to return error on next badger breaking change. + lc.cancel() +} + +// HasBeenClosed gets signaled when Signal() is called. +func (lc *Closer) HasBeenClosed() <-chan struct{} { + if lc == nil { + return dummyCloserChan + } + return lc.ctx.Done() +} + +// Done calls Done() on the WaitGroup. +func (lc *Closer) Done() { + if lc == nil { + return + } + lc.waiting.Done() +} + +// Wait waits on the WaitGroup. (It waits for NewCloser's initial value, AddRunning, and Done +// calls to balance out.) +func (lc *Closer) Wait() { + lc.waiting.Wait() +} + +// SignalAndWait calls Signal(), then Wait(). +func (lc *Closer) SignalAndWait() { + lc.Signal() + lc.Wait() +} + +// ZeroOut zeroes out all the bytes in the range [start, end). +func ZeroOut(dst []byte, start, end int) { + if start < 0 || start >= len(dst) { + return // BAD + } + if end >= len(dst) { + end = len(dst) + } + if end-start <= 0 { + return + } + Memclr(dst[start:end]) + // b := dst[start:end] + // for i := range b { + // b[i] = 0x0 + // } +} diff --git a/vendor/github.com/dgryski/go-rendezvous/LICENSE b/vendor/github.com/dgryski/go-rendezvous/LICENSE new file mode 100644 index 0000000000..22080f736a --- /dev/null +++ b/vendor/github.com/dgryski/go-rendezvous/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017-2020 Damian Gryski + +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. diff --git a/vendor/github.com/dgryski/go-rendezvous/rdv.go b/vendor/github.com/dgryski/go-rendezvous/rdv.go new file mode 100644 index 0000000000..7a6f8203c6 --- /dev/null +++ b/vendor/github.com/dgryski/go-rendezvous/rdv.go @@ -0,0 +1,79 @@ +package rendezvous + +type Rendezvous struct { + nodes map[string]int + nstr []string + nhash []uint64 + hash Hasher +} + +type Hasher func(s string) uint64 + +func New(nodes []string, hash Hasher) *Rendezvous { + r := &Rendezvous{ + nodes: make(map[string]int, len(nodes)), + nstr: make([]string, len(nodes)), + nhash: make([]uint64, len(nodes)), + hash: hash, + } + + for i, n := range nodes { + r.nodes[n] = i + r.nstr[i] = n + r.nhash[i] = hash(n) + } + + return r +} + +func (r *Rendezvous) Lookup(k string) string { + // short-circuit if we're empty + if len(r.nodes) == 0 { + return "" + } + + khash := r.hash(k) + + var midx int + var mhash = xorshiftMult64(khash ^ r.nhash[0]) + + for i, nhash := range r.nhash[1:] { + if h := xorshiftMult64(khash ^ nhash); h > mhash { + midx = i + 1 + mhash = h + } + } + + return r.nstr[midx] +} + +func (r *Rendezvous) Add(node string) { + r.nodes[node] = len(r.nstr) + r.nstr = append(r.nstr, node) + r.nhash = append(r.nhash, r.hash(node)) +} + +func (r *Rendezvous) Remove(node string) { + // find index of node to remove + nidx := r.nodes[node] + + // remove from the slices + l := len(r.nstr) + r.nstr[nidx] = r.nstr[l] + r.nstr = r.nstr[:l] + + r.nhash[nidx] = r.nhash[l] + r.nhash = r.nhash[:l] + + // update the map + delete(r.nodes, node) + moved := r.nstr[nidx] + r.nodes[moved] = nidx +} + +func xorshiftMult64(x uint64) uint64 { + x ^= x >> 12 // a + x ^= x << 25 // b + x ^= x >> 27 // c + return x * 2685821657736338717 +} diff --git a/vendor/github.com/dustin/go-humanize/.travis.yml b/vendor/github.com/dustin/go-humanize/.travis.yml new file mode 100644 index 0000000000..ba95cdd15c --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/.travis.yml @@ -0,0 +1,21 @@ +sudo: false +language: go +go: + - 1.3.x + - 1.5.x + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - master +matrix: + allow_failures: + - go: master + fast_finish: true +install: + - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d -s .) + - go tool vet . + - go test -v -race ./... diff --git a/vendor/github.com/dustin/go-humanize/LICENSE b/vendor/github.com/dustin/go-humanize/LICENSE new file mode 100644 index 0000000000..8d9a94a906 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2005-2008 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. + + diff --git a/vendor/github.com/dustin/go-humanize/README.markdown b/vendor/github.com/dustin/go-humanize/README.markdown new file mode 100644 index 0000000000..91b4ae5646 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/README.markdown @@ -0,0 +1,124 @@ +# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize) + +Just a few functions for helping humanize times and sizes. + +`go get` it as `github.com/dustin/go-humanize`, import it as +`"github.com/dustin/go-humanize"`, use it as `humanize`. + +See [godoc](https://godoc.org/github.com/dustin/go-humanize) for +complete documentation. + +## Sizes + +This lets you take numbers like `82854982` and convert them to useful +strings like, `83 MB` or `79 MiB` (whichever you prefer). + +Example: + +```go +fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB. +``` + +## Times + +This lets you take a `time.Time` and spit it out in relative terms. +For example, `12 seconds ago` or `3 days from now`. + +Example: + +```go +fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago. +``` + +Thanks to Kyle Lemons for the time implementation from an IRC +conversation one day. It's pretty neat. + +## Ordinals + +From a [mailing list discussion][odisc] where a user wanted to be able +to label ordinals. + + 0 -> 0th + 1 -> 1st + 2 -> 2nd + 3 -> 3rd + 4 -> 4th + [...] + +Example: + +```go +fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend. +``` + +## Commas + +Want to shove commas into numbers? Be my guest. + + 0 -> 0 + 100 -> 100 + 1000 -> 1,000 + 1000000000 -> 1,000,000,000 + -100000 -> -100,000 + +Example: + +```go +fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491. +``` + +## Ftoa + +Nicer float64 formatter that removes trailing zeros. + +```go +fmt.Printf("%f", 2.24) // 2.240000 +fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24 +fmt.Printf("%f", 2.0) // 2.000000 +fmt.Printf("%s", humanize.Ftoa(2.0)) // 2 +``` + +## SI notation + +Format numbers with [SI notation][sinotation]. + +Example: + +```go +humanize.SI(0.00000000223, "M") // 2.23 nM +``` + +## English-specific functions + +The following functions are in the `humanize/english` subpackage. + +### Plurals + +Simple English pluralization + +```go +english.PluralWord(1, "object", "") // object +english.PluralWord(42, "object", "") // objects +english.PluralWord(2, "bus", "") // buses +english.PluralWord(99, "locus", "loci") // loci + +english.Plural(1, "object", "") // 1 object +english.Plural(42, "object", "") // 42 objects +english.Plural(2, "bus", "") // 2 buses +english.Plural(99, "locus", "loci") // 99 loci +``` + +### Word series + +Format comma-separated words lists with conjuctions: + +```go +english.WordSeries([]string{"foo"}, "and") // foo +english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar +english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz + +english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz +``` + +[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion +[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix diff --git a/vendor/github.com/dustin/go-humanize/big.go b/vendor/github.com/dustin/go-humanize/big.go new file mode 100644 index 0000000000..f49dc337dc --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/big.go @@ -0,0 +1,31 @@ +package humanize + +import ( + "math/big" +) + +// order of magnitude (to a max order) +func oomm(n, b *big.Int, maxmag int) (float64, int) { + mag := 0 + m := &big.Int{} + for n.Cmp(b) >= 0 { + n.DivMod(n, b, m) + mag++ + if mag == maxmag && maxmag >= 0 { + break + } + } + return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag +} + +// total order of magnitude +// (same as above, but with no upper limit) +func oom(n, b *big.Int) (float64, int) { + mag := 0 + m := &big.Int{} + for n.Cmp(b) >= 0 { + n.DivMod(n, b, m) + mag++ + } + return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag +} diff --git a/vendor/github.com/dustin/go-humanize/bigbytes.go b/vendor/github.com/dustin/go-humanize/bigbytes.go new file mode 100644 index 0000000000..1a2bf61723 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/bigbytes.go @@ -0,0 +1,173 @@ +package humanize + +import ( + "fmt" + "math/big" + "strings" + "unicode" +) + +var ( + bigIECExp = big.NewInt(1024) + + // BigByte is one byte in bit.Ints + BigByte = big.NewInt(1) + // BigKiByte is 1,024 bytes in bit.Ints + BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp) + // BigMiByte is 1,024 k bytes in bit.Ints + BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp) + // BigGiByte is 1,024 m bytes in bit.Ints + BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp) + // BigTiByte is 1,024 g bytes in bit.Ints + BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp) + // BigPiByte is 1,024 t bytes in bit.Ints + BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp) + // BigEiByte is 1,024 p bytes in bit.Ints + BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp) + // BigZiByte is 1,024 e bytes in bit.Ints + BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp) + // BigYiByte is 1,024 z bytes in bit.Ints + BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp) +) + +var ( + bigSIExp = big.NewInt(1000) + + // BigSIByte is one SI byte in big.Ints + BigSIByte = big.NewInt(1) + // BigKByte is 1,000 SI bytes in big.Ints + BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp) + // BigMByte is 1,000 SI k bytes in big.Ints + BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp) + // BigGByte is 1,000 SI m bytes in big.Ints + BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp) + // BigTByte is 1,000 SI g bytes in big.Ints + BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp) + // BigPByte is 1,000 SI t bytes in big.Ints + BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp) + // BigEByte is 1,000 SI p bytes in big.Ints + BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp) + // BigZByte is 1,000 SI e bytes in big.Ints + BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp) + // BigYByte is 1,000 SI z bytes in big.Ints + BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp) +) + +var bigBytesSizeTable = map[string]*big.Int{ + "b": BigByte, + "kib": BigKiByte, + "kb": BigKByte, + "mib": BigMiByte, + "mb": BigMByte, + "gib": BigGiByte, + "gb": BigGByte, + "tib": BigTiByte, + "tb": BigTByte, + "pib": BigPiByte, + "pb": BigPByte, + "eib": BigEiByte, + "eb": BigEByte, + "zib": BigZiByte, + "zb": BigZByte, + "yib": BigYiByte, + "yb": BigYByte, + // Without suffix + "": BigByte, + "ki": BigKiByte, + "k": BigKByte, + "mi": BigMiByte, + "m": BigMByte, + "gi": BigGiByte, + "g": BigGByte, + "ti": BigTiByte, + "t": BigTByte, + "pi": BigPiByte, + "p": BigPByte, + "ei": BigEiByte, + "e": BigEByte, + "z": BigZByte, + "zi": BigZiByte, + "y": BigYByte, + "yi": BigYiByte, +} + +var ten = big.NewInt(10) + +func humanateBigBytes(s, base *big.Int, sizes []string) string { + if s.Cmp(ten) < 0 { + return fmt.Sprintf("%d B", s) + } + c := (&big.Int{}).Set(s) + val, mag := oomm(c, base, len(sizes)-1) + suffix := sizes[mag] + f := "%.0f %s" + if val < 10 { + f = "%.1f %s" + } + + return fmt.Sprintf(f, val, suffix) + +} + +// BigBytes produces a human readable representation of an SI size. +// +// See also: ParseBigBytes. +// +// BigBytes(82854982) -> 83 MB +func BigBytes(s *big.Int) string { + sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} + return humanateBigBytes(s, bigSIExp, sizes) +} + +// BigIBytes produces a human readable representation of an IEC size. +// +// See also: ParseBigBytes. +// +// BigIBytes(82854982) -> 79 MiB +func BigIBytes(s *big.Int) string { + sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} + return humanateBigBytes(s, bigIECExp, sizes) +} + +// ParseBigBytes parses a string representation of bytes into the number +// of bytes it represents. +// +// See also: BigBytes, BigIBytes. +// +// ParseBigBytes("42 MB") -> 42000000, nil +// ParseBigBytes("42 mib") -> 44040192, nil +func ParseBigBytes(s string) (*big.Int, error) { + lastDigit := 0 + hasComma := false + for _, r := range s { + if !(unicode.IsDigit(r) || r == '.' || r == ',') { + break + } + if r == ',' { + hasComma = true + } + lastDigit++ + } + + num := s[:lastDigit] + if hasComma { + num = strings.Replace(num, ",", "", -1) + } + + val := &big.Rat{} + _, err := fmt.Sscanf(num, "%f", val) + if err != nil { + return nil, err + } + + extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) + if m, ok := bigBytesSizeTable[extra]; ok { + mv := (&big.Rat{}).SetInt(m) + val.Mul(val, mv) + rv := &big.Int{} + rv.Div(val.Num(), val.Denom()) + return rv, nil + } + + return nil, fmt.Errorf("unhandled size name: %v", extra) +} diff --git a/vendor/github.com/dustin/go-humanize/bytes.go b/vendor/github.com/dustin/go-humanize/bytes.go new file mode 100644 index 0000000000..0b498f4885 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/bytes.go @@ -0,0 +1,143 @@ +package humanize + +import ( + "fmt" + "math" + "strconv" + "strings" + "unicode" +) + +// IEC Sizes. +// kibis of bits +const ( + Byte = 1 << (iota * 10) + KiByte + MiByte + GiByte + TiByte + PiByte + EiByte +) + +// SI Sizes. +const ( + IByte = 1 + KByte = IByte * 1000 + MByte = KByte * 1000 + GByte = MByte * 1000 + TByte = GByte * 1000 + PByte = TByte * 1000 + EByte = PByte * 1000 +) + +var bytesSizeTable = map[string]uint64{ + "b": Byte, + "kib": KiByte, + "kb": KByte, + "mib": MiByte, + "mb": MByte, + "gib": GiByte, + "gb": GByte, + "tib": TiByte, + "tb": TByte, + "pib": PiByte, + "pb": PByte, + "eib": EiByte, + "eb": EByte, + // Without suffix + "": Byte, + "ki": KiByte, + "k": KByte, + "mi": MiByte, + "m": MByte, + "gi": GiByte, + "g": GByte, + "ti": TiByte, + "t": TByte, + "pi": PiByte, + "p": PByte, + "ei": EiByte, + "e": EByte, +} + +func logn(n, b float64) float64 { + return math.Log(n) / math.Log(b) +} + +func humanateBytes(s uint64, base float64, sizes []string) string { + if s < 10 { + return fmt.Sprintf("%d B", s) + } + e := math.Floor(logn(float64(s), base)) + suffix := sizes[int(e)] + val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 + f := "%.0f %s" + if val < 10 { + f = "%.1f %s" + } + + return fmt.Sprintf(f, val, suffix) +} + +// Bytes produces a human readable representation of an SI size. +// +// See also: ParseBytes. +// +// Bytes(82854982) -> 83 MB +func Bytes(s uint64) string { + sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} + return humanateBytes(s, 1000, sizes) +} + +// IBytes produces a human readable representation of an IEC size. +// +// See also: ParseBytes. +// +// IBytes(82854982) -> 79 MiB +func IBytes(s uint64) string { + sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} + return humanateBytes(s, 1024, sizes) +} + +// ParseBytes parses a string representation of bytes into the number +// of bytes it represents. +// +// See Also: Bytes, IBytes. +// +// ParseBytes("42 MB") -> 42000000, nil +// ParseBytes("42 mib") -> 44040192, nil +func ParseBytes(s string) (uint64, error) { + lastDigit := 0 + hasComma := false + for _, r := range s { + if !(unicode.IsDigit(r) || r == '.' || r == ',') { + break + } + if r == ',' { + hasComma = true + } + lastDigit++ + } + + num := s[:lastDigit] + if hasComma { + num = strings.Replace(num, ",", "", -1) + } + + f, err := strconv.ParseFloat(num, 64) + if err != nil { + return 0, err + } + + extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) + if m, ok := bytesSizeTable[extra]; ok { + f *= float64(m) + if f >= math.MaxUint64 { + return 0, fmt.Errorf("too large: %v", s) + } + return uint64(f), nil + } + + return 0, fmt.Errorf("unhandled size name: %v", extra) +} diff --git a/vendor/github.com/dustin/go-humanize/comma.go b/vendor/github.com/dustin/go-humanize/comma.go new file mode 100644 index 0000000000..520ae3e57d --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/comma.go @@ -0,0 +1,116 @@ +package humanize + +import ( + "bytes" + "math" + "math/big" + "strconv" + "strings" +) + +// Comma produces a string form of the given number in base 10 with +// commas after every three orders of magnitude. +// +// e.g. Comma(834142) -> 834,142 +func Comma(v int64) string { + sign := "" + + // Min int64 can't be negated to a usable value, so it has to be special cased. + if v == math.MinInt64 { + return "-9,223,372,036,854,775,808" + } + + if v < 0 { + sign = "-" + v = 0 - v + } + + parts := []string{"", "", "", "", "", "", ""} + j := len(parts) - 1 + + for v > 999 { + parts[j] = strconv.FormatInt(v%1000, 10) + switch len(parts[j]) { + case 2: + parts[j] = "0" + parts[j] + case 1: + parts[j] = "00" + parts[j] + } + v = v / 1000 + j-- + } + parts[j] = strconv.Itoa(int(v)) + return sign + strings.Join(parts[j:], ",") +} + +// Commaf produces a string form of the given number in base 10 with +// commas after every three orders of magnitude. +// +// e.g. Commaf(834142.32) -> 834,142.32 +func Commaf(v float64) string { + buf := &bytes.Buffer{} + if v < 0 { + buf.Write([]byte{'-'}) + v = 0 - v + } + + comma := []byte{','} + + parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".") + pos := 0 + if len(parts[0])%3 != 0 { + pos += len(parts[0]) % 3 + buf.WriteString(parts[0][:pos]) + buf.Write(comma) + } + for ; pos < len(parts[0]); pos += 3 { + buf.WriteString(parts[0][pos : pos+3]) + buf.Write(comma) + } + buf.Truncate(buf.Len() - 1) + + if len(parts) > 1 { + buf.Write([]byte{'.'}) + buf.WriteString(parts[1]) + } + return buf.String() +} + +// CommafWithDigits works like the Commaf but limits the resulting +// string to the given number of decimal places. +// +// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3 +func CommafWithDigits(f float64, decimals int) string { + return stripTrailingDigits(Commaf(f), decimals) +} + +// BigComma produces a string form of the given big.Int in base 10 +// with commas after every three orders of magnitude. +func BigComma(b *big.Int) string { + sign := "" + if b.Sign() < 0 { + sign = "-" + b.Abs(b) + } + + athousand := big.NewInt(1000) + c := (&big.Int{}).Set(b) + _, m := oom(c, athousand) + parts := make([]string, m+1) + j := len(parts) - 1 + + mod := &big.Int{} + for b.Cmp(athousand) >= 0 { + b.DivMod(b, athousand, mod) + parts[j] = strconv.FormatInt(mod.Int64(), 10) + switch len(parts[j]) { + case 2: + parts[j] = "0" + parts[j] + case 1: + parts[j] = "00" + parts[j] + } + j-- + } + parts[j] = strconv.Itoa(int(b.Int64())) + return sign + strings.Join(parts[j:], ",") +} diff --git a/vendor/github.com/dustin/go-humanize/commaf.go b/vendor/github.com/dustin/go-humanize/commaf.go new file mode 100644 index 0000000000..620690dec7 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/commaf.go @@ -0,0 +1,40 @@ +// +build go1.6 + +package humanize + +import ( + "bytes" + "math/big" + "strings" +) + +// BigCommaf produces a string form of the given big.Float in base 10 +// with commas after every three orders of magnitude. +func BigCommaf(v *big.Float) string { + buf := &bytes.Buffer{} + if v.Sign() < 0 { + buf.Write([]byte{'-'}) + v.Abs(v) + } + + comma := []byte{','} + + parts := strings.Split(v.Text('f', -1), ".") + pos := 0 + if len(parts[0])%3 != 0 { + pos += len(parts[0]) % 3 + buf.WriteString(parts[0][:pos]) + buf.Write(comma) + } + for ; pos < len(parts[0]); pos += 3 { + buf.WriteString(parts[0][pos : pos+3]) + buf.Write(comma) + } + buf.Truncate(buf.Len() - 1) + + if len(parts) > 1 { + buf.Write([]byte{'.'}) + buf.WriteString(parts[1]) + } + return buf.String() +} diff --git a/vendor/github.com/dustin/go-humanize/ftoa.go b/vendor/github.com/dustin/go-humanize/ftoa.go new file mode 100644 index 0000000000..1c62b640d4 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/ftoa.go @@ -0,0 +1,46 @@ +package humanize + +import ( + "strconv" + "strings" +) + +func stripTrailingZeros(s string) string { + offset := len(s) - 1 + for offset > 0 { + if s[offset] == '.' { + offset-- + break + } + if s[offset] != '0' { + break + } + offset-- + } + return s[:offset+1] +} + +func stripTrailingDigits(s string, digits int) string { + if i := strings.Index(s, "."); i >= 0 { + if digits <= 0 { + return s[:i] + } + i++ + if i+digits >= len(s) { + return s + } + return s[:i+digits] + } + return s +} + +// Ftoa converts a float to a string with no trailing zeros. +func Ftoa(num float64) string { + return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64)) +} + +// FtoaWithDigits converts a float to a string but limits the resulting string +// to the given number of decimal places, and no trailing zeros. +func FtoaWithDigits(num float64, digits int) string { + return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits)) +} diff --git a/vendor/github.com/dustin/go-humanize/humanize.go b/vendor/github.com/dustin/go-humanize/humanize.go new file mode 100644 index 0000000000..a2c2da31ef --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/humanize.go @@ -0,0 +1,8 @@ +/* +Package humanize converts boring ugly numbers to human-friendly strings and back. + +Durations can be turned into strings such as "3 days ago", numbers +representing sizes like 82854982 into useful strings like, "83 MB" or +"79 MiB" (whichever you prefer). +*/ +package humanize diff --git a/vendor/github.com/dustin/go-humanize/number.go b/vendor/github.com/dustin/go-humanize/number.go new file mode 100644 index 0000000000..dec6186599 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/number.go @@ -0,0 +1,192 @@ +package humanize + +/* +Slightly adapted from the source to fit go-humanize. + +Author: https://github.com/gorhill +Source: https://gist.github.com/gorhill/5285193 + +*/ + +import ( + "math" + "strconv" +) + +var ( + renderFloatPrecisionMultipliers = [...]float64{ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + } + + renderFloatPrecisionRounders = [...]float64{ + 0.5, + 0.05, + 0.005, + 0.0005, + 0.00005, + 0.000005, + 0.0000005, + 0.00000005, + 0.000000005, + 0.0000000005, + } +) + +// FormatFloat produces a formatted number as string based on the following user-specified criteria: +// * thousands separator +// * decimal separator +// * decimal precision +// +// Usage: s := RenderFloat(format, n) +// The format parameter tells how to render the number n. +// +// See examples: http://play.golang.org/p/LXc1Ddm1lJ +// +// Examples of format strings, given n = 12345.6789: +// "#,###.##" => "12,345.67" +// "#,###." => "12,345" +// "#,###" => "12345,678" +// "#\u202F###,##" => "12 345,68" +// "#.###,###### => 12.345,678900 +// "" (aka default format) => 12,345.67 +// +// The highest precision allowed is 9 digits after the decimal symbol. +// There is also a version for integer number, FormatInteger(), +// which is convenient for calls within template. +func FormatFloat(format string, n float64) string { + // Special cases: + // NaN = "NaN" + // +Inf = "+Infinity" + // -Inf = "-Infinity" + if math.IsNaN(n) { + return "NaN" + } + if n > math.MaxFloat64 { + return "Infinity" + } + if n < -math.MaxFloat64 { + return "-Infinity" + } + + // default format + precision := 2 + decimalStr := "." + thousandStr := "," + positiveStr := "" + negativeStr := "-" + + if len(format) > 0 { + format := []rune(format) + + // If there is an explicit format directive, + // then default values are these: + precision = 9 + thousandStr = "" + + // collect indices of meaningful formatting directives + formatIndx := []int{} + for i, char := range format { + if char != '#' && char != '0' { + formatIndx = append(formatIndx, i) + } + } + + if len(formatIndx) > 0 { + // Directive at index 0: + // Must be a '+' + // Raise an error if not the case + // index: 0123456789 + // +0.000,000 + // +000,000.0 + // +0000.00 + // +0000 + if formatIndx[0] == 0 { + if format[formatIndx[0]] != '+' { + panic("RenderFloat(): invalid positive sign directive") + } + positiveStr = "+" + formatIndx = formatIndx[1:] + } + + // Two directives: + // First is thousands separator + // Raise an error if not followed by 3-digit + // 0123456789 + // 0.000,000 + // 000,000.00 + if len(formatIndx) == 2 { + if (formatIndx[1] - formatIndx[0]) != 4 { + panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers") + } + thousandStr = string(format[formatIndx[0]]) + formatIndx = formatIndx[1:] + } + + // One directive: + // Directive is decimal separator + // The number of digit-specifier following the separator indicates wanted precision + // 0123456789 + // 0.00 + // 000,0000 + if len(formatIndx) == 1 { + decimalStr = string(format[formatIndx[0]]) + precision = len(format) - formatIndx[0] - 1 + } + } + } + + // generate sign part + var signStr string + if n >= 0.000000001 { + signStr = positiveStr + } else if n <= -0.000000001 { + signStr = negativeStr + n = -n + } else { + signStr = "" + n = 0.0 + } + + // split number into integer and fractional parts + intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) + + // generate integer part string + intStr := strconv.FormatInt(int64(intf), 10) + + // add thousand separator if required + if len(thousandStr) > 0 { + for i := len(intStr); i > 3; { + i -= 3 + intStr = intStr[:i] + thousandStr + intStr[i:] + } + } + + // no fractional part, we can leave now + if precision == 0 { + return signStr + intStr + } + + // generate fractional part + fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision])) + // may need padding + if len(fracStr) < precision { + fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr + } + + return signStr + intStr + decimalStr + fracStr +} + +// FormatInteger produces a formatted number as string. +// See FormatFloat. +func FormatInteger(format string, n int) string { + return FormatFloat(format, float64(n)) +} diff --git a/vendor/github.com/dustin/go-humanize/ordinals.go b/vendor/github.com/dustin/go-humanize/ordinals.go new file mode 100644 index 0000000000..43d88a8619 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/ordinals.go @@ -0,0 +1,25 @@ +package humanize + +import "strconv" + +// Ordinal gives you the input number in a rank/ordinal format. +// +// Ordinal(3) -> 3rd +func Ordinal(x int) string { + suffix := "th" + switch x % 10 { + case 1: + if x%100 != 11 { + suffix = "st" + } + case 2: + if x%100 != 12 { + suffix = "nd" + } + case 3: + if x%100 != 13 { + suffix = "rd" + } + } + return strconv.Itoa(x) + suffix +} diff --git a/vendor/github.com/dustin/go-humanize/si.go b/vendor/github.com/dustin/go-humanize/si.go new file mode 100644 index 0000000000..ae659e0e49 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/si.go @@ -0,0 +1,123 @@ +package humanize + +import ( + "errors" + "math" + "regexp" + "strconv" +) + +var siPrefixTable = map[float64]string{ + -24: "y", // yocto + -21: "z", // zepto + -18: "a", // atto + -15: "f", // femto + -12: "p", // pico + -9: "n", // nano + -6: "µ", // micro + -3: "m", // milli + 0: "", + 3: "k", // kilo + 6: "M", // mega + 9: "G", // giga + 12: "T", // tera + 15: "P", // peta + 18: "E", // exa + 21: "Z", // zetta + 24: "Y", // yotta +} + +var revSIPrefixTable = revfmap(siPrefixTable) + +// revfmap reverses the map and precomputes the power multiplier +func revfmap(in map[float64]string) map[string]float64 { + rv := map[string]float64{} + for k, v := range in { + rv[v] = math.Pow(10, k) + } + return rv +} + +var riParseRegex *regexp.Regexp + +func init() { + ri := `^([\-0-9.]+)\s?([` + for _, v := range siPrefixTable { + ri += v + } + ri += `]?)(.*)` + + riParseRegex = regexp.MustCompile(ri) +} + +// ComputeSI finds the most appropriate SI prefix for the given number +// and returns the prefix along with the value adjusted to be within +// that prefix. +// +// See also: SI, ParseSI. +// +// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p") +func ComputeSI(input float64) (float64, string) { + if input == 0 { + return 0, "" + } + mag := math.Abs(input) + exponent := math.Floor(logn(mag, 10)) + exponent = math.Floor(exponent/3) * 3 + + value := mag / math.Pow(10, exponent) + + // Handle special case where value is exactly 1000.0 + // Should return 1 M instead of 1000 k + if value == 1000.0 { + exponent += 3 + value = mag / math.Pow(10, exponent) + } + + value = math.Copysign(value, input) + + prefix := siPrefixTable[exponent] + return value, prefix +} + +// SI returns a string with default formatting. +// +// SI uses Ftoa to format float value, removing trailing zeros. +// +// See also: ComputeSI, ParseSI. +// +// e.g. SI(1000000, "B") -> 1 MB +// e.g. SI(2.2345e-12, "F") -> 2.2345 pF +func SI(input float64, unit string) string { + value, prefix := ComputeSI(input) + return Ftoa(value) + " " + prefix + unit +} + +// SIWithDigits works like SI but limits the resulting string to the +// given number of decimal places. +// +// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB +// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF +func SIWithDigits(input float64, decimals int, unit string) string { + value, prefix := ComputeSI(input) + return FtoaWithDigits(value, decimals) + " " + prefix + unit +} + +var errInvalid = errors.New("invalid input") + +// ParseSI parses an SI string back into the number and unit. +// +// See also: SI, ComputeSI. +// +// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil) +func ParseSI(input string) (float64, string, error) { + found := riParseRegex.FindStringSubmatch(input) + if len(found) != 4 { + return 0, "", errInvalid + } + mag := revSIPrefixTable[found[2]] + unit := found[3] + + base, err := strconv.ParseFloat(found[1], 64) + return base * mag, unit, err +} diff --git a/vendor/github.com/dustin/go-humanize/times.go b/vendor/github.com/dustin/go-humanize/times.go new file mode 100644 index 0000000000..dd3fbf5efc --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/times.go @@ -0,0 +1,117 @@ +package humanize + +import ( + "fmt" + "math" + "sort" + "time" +) + +// Seconds-based time units +const ( + Day = 24 * time.Hour + Week = 7 * Day + Month = 30 * Day + Year = 12 * Month + LongTime = 37 * Year +) + +// Time formats a time into a relative string. +// +// Time(someT) -> "3 weeks ago" +func Time(then time.Time) string { + return RelTime(then, time.Now(), "ago", "from now") +} + +// A RelTimeMagnitude struct contains a relative time point at which +// the relative format of time will switch to a new format string. A +// slice of these in ascending order by their "D" field is passed to +// CustomRelTime to format durations. +// +// The Format field is a string that may contain a "%s" which will be +// replaced with the appropriate signed label (e.g. "ago" or "from +// now") and a "%d" that will be replaced by the quantity. +// +// The DivBy field is the amount of time the time difference must be +// divided by in order to display correctly. +// +// e.g. if D is 2*time.Minute and you want to display "%d minutes %s" +// DivBy should be time.Minute so whatever the duration is will be +// expressed in minutes. +type RelTimeMagnitude struct { + D time.Duration + Format string + DivBy time.Duration +} + +var defaultMagnitudes = []RelTimeMagnitude{ + {time.Second, "now", time.Second}, + {2 * time.Second, "1 second %s", 1}, + {time.Minute, "%d seconds %s", time.Second}, + {2 * time.Minute, "1 minute %s", 1}, + {time.Hour, "%d minutes %s", time.Minute}, + {2 * time.Hour, "1 hour %s", 1}, + {Day, "%d hours %s", time.Hour}, + {2 * Day, "1 day %s", 1}, + {Week, "%d days %s", Day}, + {2 * Week, "1 week %s", 1}, + {Month, "%d weeks %s", Week}, + {2 * Month, "1 month %s", 1}, + {Year, "%d months %s", Month}, + {18 * Month, "1 year %s", 1}, + {2 * Year, "2 years %s", 1}, + {LongTime, "%d years %s", Year}, + {math.MaxInt64, "a long while %s", 1}, +} + +// RelTime formats a time into a relative string. +// +// It takes two times and two labels. In addition to the generic time +// delta string (e.g. 5 minutes), the labels are used applied so that +// the label corresponding to the smaller time is applied. +// +// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier" +func RelTime(a, b time.Time, albl, blbl string) string { + return CustomRelTime(a, b, albl, blbl, defaultMagnitudes) +} + +// CustomRelTime formats a time into a relative string. +// +// It takes two times two labels and a table of relative time formats. +// In addition to the generic time delta string (e.g. 5 minutes), the +// labels are used applied so that the label corresponding to the +// smaller time is applied. +func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string { + lbl := albl + diff := b.Sub(a) + + if a.After(b) { + lbl = blbl + diff = a.Sub(b) + } + + n := sort.Search(len(magnitudes), func(i int) bool { + return magnitudes[i].D > diff + }) + + if n >= len(magnitudes) { + n = len(magnitudes) - 1 + } + mag := magnitudes[n] + args := []interface{}{} + escaped := false + for _, ch := range mag.Format { + if escaped { + switch ch { + case 's': + args = append(args, lbl) + case 'd': + args = append(args, diff/mag.DivBy) + } + escaped = false + } else { + escaped = ch == '%' + } + } + return fmt.Sprintf(mag.Format, args...) +} diff --git a/vendor/github.com/go-chi/chi/.gitignore b/vendor/github.com/go-chi/chi/.gitignore new file mode 100644 index 0000000000..ba22c99a99 --- /dev/null +++ b/vendor/github.com/go-chi/chi/.gitignore @@ -0,0 +1,3 @@ +.idea +*.sw? +.vscode diff --git a/vendor/github.com/go-chi/chi/.travis.yml b/vendor/github.com/go-chi/chi/.travis.yml new file mode 100644 index 0000000000..7b8e26bcee --- /dev/null +++ b/vendor/github.com/go-chi/chi/.travis.yml @@ -0,0 +1,20 @@ +language: go + +go: + - 1.10.x + - 1.11.x + - 1.12.x + - 1.13.x + - 1.14.x + +script: + - go get -d -t ./... + - go vet ./... + - go test ./... + - > + go_version=$(go version); + if [ ${go_version:13:4} = "1.12" ]; then + go get -u golang.org/x/tools/cmd/goimports; + goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :; + fi + diff --git a/vendor/github.com/go-chi/chi/CHANGELOG.md b/vendor/github.com/go-chi/chi/CHANGELOG.md new file mode 100644 index 0000000000..9a64a72eec --- /dev/null +++ b/vendor/github.com/go-chi/chi/CHANGELOG.md @@ -0,0 +1,190 @@ +# Changelog + +## v4.1.2 (2020-06-02) + +- fix that handles MethodNotAllowed with path variables, thank you @caseyhadden for your contribution +- fix to replace nested wildcards correctly in RoutePattern, thank you @@unmultimedio for your contribution +- History of changes: see https://github.com/go-chi/chi/compare/v4.1.1...v4.1.2 + + +## v4.1.1 (2020-04-16) + +- fix for issue https://github.com/go-chi/chi/issues/411 which allows for overlapping regexp + route to the correct handler through a recursive tree search, thanks to @Jahaja for the PR/fix! +- new middleware.RouteHeaders as a simple router for request headers with wildcard support +- History of changes: see https://github.com/go-chi/chi/compare/v4.1.0...v4.1.1 + + +## v4.1.0 (2020-04-1) + +- middleware.LogEntry: Write method on interface now passes the response header + and an extra interface type useful for custom logger implementations. +- middleware.WrapResponseWriter: minor fix +- middleware.Recoverer: a bit prettier +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.4...v4.1.0 + + +## v4.0.4 (2020-03-24) + +- middleware.Recoverer: new pretty stack trace printing (https://github.com/go-chi/chi/pull/496) +- a few minor improvements and fixes +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.3...v4.0.4 + + +## v4.0.3 (2020-01-09) + +- core: fix regexp routing to include default value when param is not matched +- middleware: rewrite of middleware.Compress +- middleware: suppress http.ErrAbortHandler in middleware.Recoverer +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.2...v4.0.3 + + +## v4.0.2 (2019-02-26) + +- Minor fixes +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.1...v4.0.2 + + +## v4.0.1 (2019-01-21) + +- Fixes issue with compress middleware: #382 #385 +- History of changes: see https://github.com/go-chi/chi/compare/v4.0.0...v4.0.1 + + +## v4.0.0 (2019-01-10) + +- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8 +- router: respond with 404 on router with no routes (#362) +- router: additional check to ensure wildcard is at the end of a url pattern (#333) +- middleware: deprecate use of http.CloseNotifier (#347) +- middleware: fix RedirectSlashes to include query params on redirect (#334) +- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0 + + +## v3.3.4 (2019-01-07) + +- Minor middleware improvements. No changes to core library/router. Moving v3 into its +- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11 +- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4 + + +## v3.3.3 (2018-08-27) + +- Minor release +- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3 + + +## v3.3.2 (2017-12-22) + +- Support to route trailing slashes on mounted sub-routers (#281) +- middleware: new `ContentCharset` to check matching charsets. Thank you + @csucu for your community contribution! + + +## v3.3.1 (2017-11-20) + +- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types +- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value +- Minor bug fixes + + +## v3.3.0 (2017-10-10) + +- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage +- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function + + +## v3.2.1 (2017-08-31) + +- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface + and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path +- Add new `RouteMethod` to `*Context` +- Add new `Routes` pointer to `*Context` +- Add new `middleware.GetHead` to route missing HEAD requests to GET handler +- Updated benchmarks (see README) + + +## v3.1.5 (2017-08-02) + +- Setup golint and go vet for the project +- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler` + to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler` + + +## v3.1.0 (2017-07-10) + +- Fix a few minor issues after v3 release +- Move `docgen` sub-pkg to https://github.com/go-chi/docgen +- Move `render` sub-pkg to https://github.com/go-chi/render +- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime + suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in + https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage. + + +## v3.0.0 (2017-06-21) + +- Major update to chi library with many exciting updates, but also some *breaking changes* +- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as + `/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the + same router +- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example: + `r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")` +- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as + `r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like + in `_examples/custom-handler` +- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their + own using file handler with the stdlib, see `_examples/fileserver` for an example +- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()` +- Moved the chi project to its own organization, to allow chi-related community packages to + be easily discovered and supported, at: https://github.com/go-chi +- *NOTE:* please update your import paths to `"github.com/go-chi/chi"` +- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2 + + +## v2.1.0 (2017-03-30) + +- Minor improvements and update to the chi core library +- Introduced a brand new `chi/render` sub-package to complete the story of building + APIs to offer a pattern for managing well-defined request / response payloads. Please + check out the updated `_examples/rest` example for how it works. +- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface + + +## v2.0.0 (2017-01-06) + +- After many months of v2 being in an RC state with many companies and users running it in + production, the inclusion of some improvements to the middlewares, we are very pleased to + announce v2.0.0 of chi. + + +## v2.0.0-rc1 (2016-07-26) + +- Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular + community `"net/context"` package has been included in the standard library as `"context"` and + utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other + request-scoped values. We're very excited about the new context addition and are proud to + introduce chi v2, a minimal and powerful routing package for building large HTTP services, + with zero external dependencies. Chi focuses on idiomatic design and encourages the use of + stdlib HTTP handlers and middlwares. +- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc` +- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()` +- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`, + which provides direct access to URL routing parameters, the routing path and the matching + routing patterns. +- Users upgrading from chi v1 to v2, need to: + 1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to + the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)` + 2. Use `chi.URLParam(r *http.Request, paramKey string) string` + or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value + + +## v1.0.0 (2016-07-01) + +- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older. + + +## v0.9.0 (2016-03-31) + +- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33) +- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters + has changed to: `chi.URLParam(ctx, "id")` diff --git a/vendor/github.com/go-chi/chi/CONTRIBUTING.md b/vendor/github.com/go-chi/chi/CONTRIBUTING.md new file mode 100644 index 0000000000..c0ac2dfe85 --- /dev/null +++ b/vendor/github.com/go-chi/chi/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing + +## Prerequisites + +1. [Install Go][go-install]. +2. Download the sources and switch the working directory: + + ```bash + go get -u -d github.com/go-chi/chi + cd $GOPATH/src/github.com/go-chi/chi + ``` + +## Submitting a Pull Request + +A typical workflow is: + +1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip] +2. [Create a topic branch.][branch] +3. Add tests for your change. +4. Run `go test`. If your tests pass, return to the step 3. +5. Implement the change and ensure the steps from the previous step pass. +6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline. +7. [Add, commit and push your changes.][git-help] +8. [Submit a pull request.][pull-req] + +[go-install]: https://golang.org/doc/install +[go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html +[fork]: https://help.github.com/articles/fork-a-repo +[branch]: http://learn.github.com/p/branching.html +[git-help]: https://guides.github.com +[pull-req]: https://help.github.com/articles/using-pull-requests diff --git a/vendor/github.com/go-chi/chi/LICENSE b/vendor/github.com/go-chi/chi/LICENSE new file mode 100644 index 0000000000..d99f02ffac --- /dev/null +++ b/vendor/github.com/go-chi/chi/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc. + +MIT License + +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. diff --git a/vendor/github.com/go-chi/chi/README.md b/vendor/github.com/go-chi/chi/README.md new file mode 100644 index 0000000000..5a8fc9d096 --- /dev/null +++ b/vendor/github.com/go-chi/chi/README.md @@ -0,0 +1,441 @@ +# chi + + +[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis] + +`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's +especially good at helping you write large REST API services that are kept maintainable as your +project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to +handle signaling, cancelation and request-scoped values across a handler chain. + +The focus of the project has been to seek out an elegant and comfortable design for writing +REST API servers, written during the development of the Pressly API service that powers our +public API service, which in turn powers all of our client-side applications. + +The key considerations of chi's design are: project structure, maintainability, standard http +handlers (stdlib-only), developer productivity, and deconstructing a large system into many small +parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also +included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too! + +## Install + +`go get -u github.com/go-chi/chi` + + +## Features + +* **Lightweight** - cloc'd in ~1000 LOC for the chi router +* **Fast** - yes, see [benchmarks](#benchmarks) +* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http` +* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting +* **Context control** - built on new `context` package, providing value chaining, cancellations and timeouts +* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91)) +* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown +* **No external dependencies** - plain ol' Go stdlib + net/http + + +## Examples + +See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples. + + +**As easy as:** + +```go +package main + +import ( + "net/http" + + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" +) + +func main() { + r := chi.NewRouter() + r.Use(middleware.Logger) + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("welcome")) + }) + http.ListenAndServe(":3000", r) +} +``` + +**REST Preview:** + +Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs +in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in +Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)). + +I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed +above, they will show you all the features of chi and serve as a good form of documentation. + +```go +import ( + //... + "context" + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" +) + +func main() { + r := chi.NewRouter() + + // A good base middleware stack + r.Use(middleware.RequestID) + r.Use(middleware.RealIP) + r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + + // Set a timeout value on the request context (ctx), that will signal + // through ctx.Done() that the request has timed out and further + // processing should be stopped. + r.Use(middleware.Timeout(60 * time.Second)) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hi")) + }) + + // RESTy routes for "articles" resource + r.Route("/articles", func(r chi.Router) { + r.With(paginate).Get("/", listArticles) // GET /articles + r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017 + + r.Post("/", createArticle) // POST /articles + r.Get("/search", searchArticles) // GET /articles/search + + // Regexp url parameters: + r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto + + // Subrouters: + r.Route("/{articleID}", func(r chi.Router) { + r.Use(ArticleCtx) + r.Get("/", getArticle) // GET /articles/123 + r.Put("/", updateArticle) // PUT /articles/123 + r.Delete("/", deleteArticle) // DELETE /articles/123 + }) + }) + + // Mount the admin sub-router + r.Mount("/admin", adminRouter()) + + http.ListenAndServe(":3333", r) +} + +func ArticleCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + articleID := chi.URLParam(r, "articleID") + article, err := dbGetArticle(articleID) + if err != nil { + http.Error(w, http.StatusText(404), 404) + return + } + ctx := context.WithValue(r.Context(), "article", article) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func getArticle(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + article, ok := ctx.Value("article").(*Article) + if !ok { + http.Error(w, http.StatusText(422), 422) + return + } + w.Write([]byte(fmt.Sprintf("title:%s", article.Title))) +} + +// A completely separate router for administrator routes +func adminRouter() http.Handler { + r := chi.NewRouter() + r.Use(AdminOnly) + r.Get("/", adminIndex) + r.Get("/accounts", adminListAccounts) + return r +} + +func AdminOnly(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + perm, ok := ctx.Value("acl.permission").(YourPermissionType) + if !ok || !perm.IsAdmin() { + http.Error(w, http.StatusText(403), 403) + return + } + next.ServeHTTP(w, r) + }) +} +``` + + +## Router design + +chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree). +The router is fully compatible with `net/http`. + +Built on top of the tree is the `Router` interface: + +```go +// Router consisting of the core routing methods used by chi's Mux, +// using only the standard net/http. +type Router interface { + http.Handler + Routes + + // Use appends one or more middlewares onto the Router stack. + Use(middlewares ...func(http.Handler) http.Handler) + + // With adds inline middlewares for an endpoint handler. + With(middlewares ...func(http.Handler) http.Handler) Router + + // Group adds a new inline-Router along the current routing + // path, with a fresh middleware stack for the inline-Router. + Group(fn func(r Router)) Router + + // Route mounts a sub-Router along a `pattern`` string. + Route(pattern string, fn func(r Router)) Router + + // Mount attaches another http.Handler along ./pattern/* + Mount(pattern string, h http.Handler) + + // Handle and HandleFunc adds routes for `pattern` that matches + // all HTTP methods. + Handle(pattern string, h http.Handler) + HandleFunc(pattern string, h http.HandlerFunc) + + // Method and MethodFunc adds routes for `pattern` that matches + // the `method` HTTP method. + Method(method, pattern string, h http.Handler) + MethodFunc(method, pattern string, h http.HandlerFunc) + + // HTTP-method routing along `pattern` + Connect(pattern string, h http.HandlerFunc) + Delete(pattern string, h http.HandlerFunc) + Get(pattern string, h http.HandlerFunc) + Head(pattern string, h http.HandlerFunc) + Options(pattern string, h http.HandlerFunc) + Patch(pattern string, h http.HandlerFunc) + Post(pattern string, h http.HandlerFunc) + Put(pattern string, h http.HandlerFunc) + Trace(pattern string, h http.HandlerFunc) + + // NotFound defines a handler to respond whenever a route could + // not be found. + NotFound(h http.HandlerFunc) + + // MethodNotAllowed defines a handler to respond whenever a method is + // not allowed. + MethodNotAllowed(h http.HandlerFunc) +} + +// Routes interface adds two methods for router traversal, which is also +// used by the github.com/go-chi/docgen package to generate documentation for Routers. +type Routes interface { + // Routes returns the routing tree in an easily traversable structure. + Routes() []Route + + // Middlewares returns the list of middlewares in use by the router. + Middlewares() Middlewares + + // Match searches the routing tree for a handler that matches + // the method/path - similar to routing a http request, but without + // executing the handler thereafter. + Match(rctx *Context, method, path string) bool +} +``` + +Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern +supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters +can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters +and `chi.URLParam(r, "*")` for a wildcard parameter. + + +### Middleware handlers + +chi's middlewares are just stdlib net/http middleware handlers. There is nothing special +about them, which means the router and all the tooling is designed to be compatible and +friendly with any middleware in the community. This offers much better extensibility and reuse +of packages and is at the heart of chi's purpose. + +Here is an example of a standard net/http middleware handler using the new request context +available in Go. This middleware sets a hypothetical user identifier on the request +context and calls the next handler in the chain. + +```go +// HTTP middleware setting a value on the request context +func MyMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := context.WithValue(r.Context(), "user", "123") + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} +``` + + +### Request handlers + +chi uses standard net/http request handlers. This little snippet is an example of a http.Handler +func that reads a user identifier from the request context - hypothetically, identifying +the user sending an authenticated request, validated+set by a previous middleware handler. + +```go +// HTTP handler accessing data from the request context. +func MyRequestHandler(w http.ResponseWriter, r *http.Request) { + user := r.Context().Value("user").(string) + w.Write([]byte(fmt.Sprintf("hi %s", user))) +} +``` + + +### URL parameters + +chi's router parses and stores URL parameters right onto the request context. Here is +an example of how to access URL params in your net/http handlers. And of course, middlewares +are able to access the same information. + +```go +// HTTP handler accessing the url routing parameters. +func MyRequestHandler(w http.ResponseWriter, r *http.Request) { + userID := chi.URLParam(r, "userID") // from a route like /users/{userID} + + ctx := r.Context() + key := ctx.Value("key").(string) + + w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key))) +} +``` + + +## Middlewares + +chi comes equipped with an optional `middleware` package, providing a suite of standard +`net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible +with `net/http` can be used with chi's mux. + +### Core middlewares + +----------------------------------------------------------------------------------------------------------- +| chi/middleware Handler | description | +|:----------------------|:--------------------------------------------------------------------------------- +| AllowContentType | Explicit whitelist of accepted request Content-Types | +| BasicAuth | Basic HTTP authentication | +| Compress | Gzip compression for clients that accept compressed responses | +| GetHead | Automatically route undefined HEAD requests to GET handlers | +| Heartbeat | Monitoring endpoint to check the servers pulse | +| Logger | Logs the start and end of each request with the elapsed processing time | +| NoCache | Sets response headers to prevent clients from caching | +| Profiler | Easily attach net/http/pprof to your routers | +| RealIP | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP | +| Recoverer | Gracefully absorb panics and prints the stack trace | +| RequestID | Injects a request ID into the context of each request | +| RedirectSlashes | Redirect slashes on routing paths | +| SetHeader | Short-hand middleware to set a response header key/value | +| StripSlashes | Strip slashes on routing paths | +| Throttle | Puts a ceiling on the number of concurrent requests | +| Timeout | Signals to the request context when the timeout deadline is reached | +| URLFormat | Parse extension from url and put it on request context | +| WithValue | Short-hand middleware to set a key/value on the request context | +----------------------------------------------------------------------------------------------------------- + +### Extra middlewares & packages + +Please see https://github.com/go-chi for additional packages. + +-------------------------------------------------------------------------------------------------------------------- +| package | description | +|:---------------------------------------------------|:------------------------------------------------------------- +| [cors](https://github.com/go-chi/cors) | Cross-origin resource sharing (CORS) | +| [docgen](https://github.com/go-chi/docgen) | Print chi.Router routes at runtime | +| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication | +| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing | +| [httplog](https://github.com/go-chi/httplog) | Small but powerful structured HTTP request logging | +| [httprate](https://github.com/go-chi/httprate) | HTTP request rate limiter | +| [httptracer](https://github.com/go-chi/httptracer) | HTTP request performance tracing library | +| [httpvcr](https://github.com/go-chi/httpvcr) | Write deterministic tests for external sources | +| [stampede](https://github.com/go-chi/stampede) | HTTP request coalescer | +-------------------------------------------------------------------------------------------------------------------- + +please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware + + +## context? + +`context` is a tiny pkg that provides simple interface to signal context across call stacks +and goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani) +and is available in stdlib since go1.7. + +Learn more at https://blog.golang.org/context + +and.. +* Docs: https://golang.org/pkg/context +* Source: https://github.com/golang/go/tree/master/src/context + + +## Benchmarks + +The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark + +Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop + +```shell +BenchmarkChi_Param 3000000 475 ns/op 432 B/op 3 allocs/op +BenchmarkChi_Param5 2000000 696 ns/op 432 B/op 3 allocs/op +BenchmarkChi_Param20 1000000 1275 ns/op 432 B/op 3 allocs/op +BenchmarkChi_ParamWrite 3000000 505 ns/op 432 B/op 3 allocs/op +BenchmarkChi_GithubStatic 3000000 508 ns/op 432 B/op 3 allocs/op +BenchmarkChi_GithubParam 2000000 669 ns/op 432 B/op 3 allocs/op +BenchmarkChi_GithubAll 10000 134627 ns/op 87699 B/op 609 allocs/op +BenchmarkChi_GPlusStatic 3000000 402 ns/op 432 B/op 3 allocs/op +BenchmarkChi_GPlusParam 3000000 500 ns/op 432 B/op 3 allocs/op +BenchmarkChi_GPlus2Params 3000000 586 ns/op 432 B/op 3 allocs/op +BenchmarkChi_GPlusAll 200000 7237 ns/op 5616 B/op 39 allocs/op +BenchmarkChi_ParseStatic 3000000 408 ns/op 432 B/op 3 allocs/op +BenchmarkChi_ParseParam 3000000 488 ns/op 432 B/op 3 allocs/op +BenchmarkChi_Parse2Params 3000000 551 ns/op 432 B/op 3 allocs/op +BenchmarkChi_ParseAll 100000 13508 ns/op 11232 B/op 78 allocs/op +BenchmarkChi_StaticAll 20000 81933 ns/op 67826 B/op 471 allocs/op +``` + +Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc + +NOTE: the allocs in the benchmark above are from the calls to http.Request's +`WithContext(context.Context)` method that clones the http.Request, sets the `Context()` +on the duplicated (alloc'd) request and returns it the new request object. This is just +how setting context on a request in Go works. + + +## Credits + +* Carl Jackson for https://github.com/zenazn/goji + * Parts of chi's thinking comes from goji, and chi's middleware package + sources from goji. +* Armon Dadgar for https://github.com/armon/go-radix +* Contributions: [@VojtechVitek](https://github.com/VojtechVitek) + +We'll be more than happy to see [your contributions](./CONTRIBUTING.md)! + + +## Beyond REST + +chi is just a http router that lets you decompose request handling into many smaller layers. +Many companies use chi to write REST services for their public APIs. But, REST is just a convention +for managing state via HTTP, and there's a lot of other pieces required to write a complete client-server +system or network of microservices. + +Looking beyond REST, I also recommend some newer works in the field: +* [webrpc](https://github.com/webrpc/webrpc) - Web-focused RPC client+server framework with code-gen +* [gRPC](https://github.com/grpc/grpc-go) - Google's RPC framework via protobufs +* [graphql](https://github.com/99designs/gqlgen) - Declarative query language +* [NATS](https://nats.io) - lightweight pub-sub + + +## License + +Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka) + +Licensed under [MIT License](./LICENSE) + +[GoDoc]: https://godoc.org/github.com/go-chi/chi +[GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg +[Travis]: https://travis-ci.org/go-chi/chi +[Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master diff --git a/vendor/github.com/go-chi/chi/chain.go b/vendor/github.com/go-chi/chi/chain.go new file mode 100644 index 0000000000..88e6846138 --- /dev/null +++ b/vendor/github.com/go-chi/chi/chain.go @@ -0,0 +1,49 @@ +package chi + +import "net/http" + +// Chain returns a Middlewares type from a slice of middleware handlers. +func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares { + return Middlewares(middlewares) +} + +// Handler builds and returns a http.Handler from the chain of middlewares, +// with `h http.Handler` as the final handler. +func (mws Middlewares) Handler(h http.Handler) http.Handler { + return &ChainHandler{mws, h, chain(mws, h)} +} + +// HandlerFunc builds and returns a http.Handler from the chain of middlewares, +// with `h http.Handler` as the final handler. +func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler { + return &ChainHandler{mws, h, chain(mws, h)} +} + +// ChainHandler is a http.Handler with support for handler composition and +// execution. +type ChainHandler struct { + Middlewares Middlewares + Endpoint http.Handler + chain http.Handler +} + +func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + c.chain.ServeHTTP(w, r) +} + +// chain builds a http.Handler composed of an inline middleware stack and endpoint +// handler in the order they are passed. +func chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler { + // Return ahead of time if there aren't any middlewares for the chain + if len(middlewares) == 0 { + return endpoint + } + + // Wrap the end handler with the middleware chain + h := middlewares[len(middlewares)-1](endpoint) + for i := len(middlewares) - 2; i >= 0; i-- { + h = middlewares[i](h) + } + + return h +} diff --git a/vendor/github.com/go-chi/chi/chi.go b/vendor/github.com/go-chi/chi/chi.go new file mode 100644 index 0000000000..b7063dc297 --- /dev/null +++ b/vendor/github.com/go-chi/chi/chi.go @@ -0,0 +1,134 @@ +// +// Package chi is a small, idiomatic and composable router for building HTTP services. +// +// chi requires Go 1.10 or newer. +// +// Example: +// package main +// +// import ( +// "net/http" +// +// "github.com/go-chi/chi" +// "github.com/go-chi/chi/middleware" +// ) +// +// func main() { +// r := chi.NewRouter() +// r.Use(middleware.Logger) +// r.Use(middleware.Recoverer) +// +// r.Get("/", func(w http.ResponseWriter, r *http.Request) { +// w.Write([]byte("root.")) +// }) +// +// http.ListenAndServe(":3333", r) +// } +// +// See github.com/go-chi/chi/_examples/ for more in-depth examples. +// +// URL patterns allow for easy matching of path components in HTTP +// requests. The matching components can then be accessed using +// chi.URLParam(). All patterns must begin with a slash. +// +// A simple named placeholder {name} matches any sequence of characters +// up to the next / or the end of the URL. Trailing slashes on paths must +// be handled explicitly. +// +// A placeholder with a name followed by a colon allows a regular +// expression match, for example {number:\\d+}. The regular expression +// syntax is Go's normal regexp RE2 syntax, except that regular expressions +// including { or } are not supported, and / will never be +// matched. An anonymous regexp pattern is allowed, using an empty string +// before the colon in the placeholder, such as {:\\d+} +// +// The special placeholder of asterisk matches the rest of the requested +// URL. Any trailing characters in the pattern are ignored. This is the only +// placeholder which will match / characters. +// +// Examples: +// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/" +// "/user/{name}/info" matches "/user/jsmith/info" +// "/page/*" matches "/page/intro/latest" +// "/page/*/index" also matches "/page/intro/latest" +// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01" +// +package chi + +import "net/http" + +// NewRouter returns a new Mux object that implements the Router interface. +func NewRouter() *Mux { + return NewMux() +} + +// Router consisting of the core routing methods used by chi's Mux, +// using only the standard net/http. +type Router interface { + http.Handler + Routes + + // Use appends one or more middlewares onto the Router stack. + Use(middlewares ...func(http.Handler) http.Handler) + + // With adds inline middlewares for an endpoint handler. + With(middlewares ...func(http.Handler) http.Handler) Router + + // Group adds a new inline-Router along the current routing + // path, with a fresh middleware stack for the inline-Router. + Group(fn func(r Router)) Router + + // Route mounts a sub-Router along a `pattern`` string. + Route(pattern string, fn func(r Router)) Router + + // Mount attaches another http.Handler along ./pattern/* + Mount(pattern string, h http.Handler) + + // Handle and HandleFunc adds routes for `pattern` that matches + // all HTTP methods. + Handle(pattern string, h http.Handler) + HandleFunc(pattern string, h http.HandlerFunc) + + // Method and MethodFunc adds routes for `pattern` that matches + // the `method` HTTP method. + Method(method, pattern string, h http.Handler) + MethodFunc(method, pattern string, h http.HandlerFunc) + + // HTTP-method routing along `pattern` + Connect(pattern string, h http.HandlerFunc) + Delete(pattern string, h http.HandlerFunc) + Get(pattern string, h http.HandlerFunc) + Head(pattern string, h http.HandlerFunc) + Options(pattern string, h http.HandlerFunc) + Patch(pattern string, h http.HandlerFunc) + Post(pattern string, h http.HandlerFunc) + Put(pattern string, h http.HandlerFunc) + Trace(pattern string, h http.HandlerFunc) + + // NotFound defines a handler to respond whenever a route could + // not be found. + NotFound(h http.HandlerFunc) + + // MethodNotAllowed defines a handler to respond whenever a method is + // not allowed. + MethodNotAllowed(h http.HandlerFunc) +} + +// Routes interface adds two methods for router traversal, which is also +// used by the `docgen` subpackage to generation documentation for Routers. +type Routes interface { + // Routes returns the routing tree in an easily traversable structure. + Routes() []Route + + // Middlewares returns the list of middlewares in use by the router. + Middlewares() Middlewares + + // Match searches the routing tree for a handler that matches + // the method/path - similar to routing a http request, but without + // executing the handler thereafter. + Match(rctx *Context, method, path string) bool +} + +// Middlewares type is a slice of standard middleware handlers with methods +// to compose middleware chains and http.Handler's. +type Middlewares []func(http.Handler) http.Handler diff --git a/vendor/github.com/go-chi/chi/context.go b/vendor/github.com/go-chi/chi/context.go new file mode 100644 index 0000000000..26c609ea2c --- /dev/null +++ b/vendor/github.com/go-chi/chi/context.go @@ -0,0 +1,172 @@ +package chi + +import ( + "context" + "net" + "net/http" + "strings" +) + +// URLParam returns the url parameter from a http.Request object. +func URLParam(r *http.Request, key string) string { + if rctx := RouteContext(r.Context()); rctx != nil { + return rctx.URLParam(key) + } + return "" +} + +// URLParamFromCtx returns the url parameter from a http.Request Context. +func URLParamFromCtx(ctx context.Context, key string) string { + if rctx := RouteContext(ctx); rctx != nil { + return rctx.URLParam(key) + } + return "" +} + +// RouteContext returns chi's routing Context object from a +// http.Request Context. +func RouteContext(ctx context.Context) *Context { + val, _ := ctx.Value(RouteCtxKey).(*Context) + return val +} + +// ServerBaseContext wraps an http.Handler to set the request context to the +// `baseCtx`. +func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler { + fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + baseCtx := baseCtx + + // Copy over default net/http server context keys + if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok { + baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v) + } + if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok { + baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v) + } + + h.ServeHTTP(w, r.WithContext(baseCtx)) + }) + return fn +} + +// NewRouteContext returns a new routing Context object. +func NewRouteContext() *Context { + return &Context{} +} + +var ( + // RouteCtxKey is the context.Context key to store the request context. + RouteCtxKey = &contextKey{"RouteContext"} +) + +// Context is the default routing context set on the root node of a +// request context to track route patterns, URL parameters and +// an optional routing path. +type Context struct { + Routes Routes + + // Routing path/method override used during the route search. + // See Mux#routeHTTP method. + RoutePath string + RouteMethod string + + // Routing pattern stack throughout the lifecycle of the request, + // across all connected routers. It is a record of all matching + // patterns across a stack of sub-routers. + RoutePatterns []string + + // URLParams are the stack of routeParams captured during the + // routing lifecycle across a stack of sub-routers. + URLParams RouteParams + + // The endpoint routing pattern that matched the request URI path + // or `RoutePath` of the current sub-router. This value will update + // during the lifecycle of a request passing through a stack of + // sub-routers. + routePattern string + + // Route parameters matched for the current sub-router. It is + // intentionally unexported so it cant be tampered. + routeParams RouteParams + + // methodNotAllowed hint + methodNotAllowed bool +} + +// Reset a routing context to its initial state. +func (x *Context) Reset() { + x.Routes = nil + x.RoutePath = "" + x.RouteMethod = "" + x.RoutePatterns = x.RoutePatterns[:0] + x.URLParams.Keys = x.URLParams.Keys[:0] + x.URLParams.Values = x.URLParams.Values[:0] + + x.routePattern = "" + x.routeParams.Keys = x.routeParams.Keys[:0] + x.routeParams.Values = x.routeParams.Values[:0] + x.methodNotAllowed = false +} + +// URLParam returns the corresponding URL parameter value from the request +// routing context. +func (x *Context) URLParam(key string) string { + for k := len(x.URLParams.Keys) - 1; k >= 0; k-- { + if x.URLParams.Keys[k] == key { + return x.URLParams.Values[k] + } + } + return "" +} + +// RoutePattern builds the routing pattern string for the particular +// request, at the particular point during routing. This means, the value +// will change throughout the execution of a request in a router. That is +// why its advised to only use this value after calling the next handler. +// +// For example, +// +// func Instrument(next http.Handler) http.Handler { +// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +// next.ServeHTTP(w, r) +// routePattern := chi.RouteContext(r.Context()).RoutePattern() +// measure(w, r, routePattern) +// }) +// } +func (x *Context) RoutePattern() string { + routePattern := strings.Join(x.RoutePatterns, "") + return replaceWildcards(routePattern) +} + +// replaceWildcards takes a route pattern and recursively replaces all +// occurrences of "/*/" to "/". +func replaceWildcards(p string) string { + if strings.Contains(p, "/*/") { + return replaceWildcards(strings.Replace(p, "/*/", "/", -1)) + } + + return p +} + +// RouteParams is a structure to track URL routing parameters efficiently. +type RouteParams struct { + Keys, Values []string +} + +// Add will append a URL parameter to the end of the route param +func (s *RouteParams) Add(key, value string) { + s.Keys = append(s.Keys, key) + s.Values = append(s.Values, value) +} + +// contextKey is a value for use with context.WithValue. It's used as +// a pointer so it fits in an interface{} without allocation. This technique +// for defining context keys was copied from Go 1.7's new use of context in net/http. +type contextKey struct { + name string +} + +func (k *contextKey) String() string { + return "chi context value " + k.name +} diff --git a/vendor/github.com/go-chi/chi/middleware/basic_auth.go b/vendor/github.com/go-chi/chi/middleware/basic_auth.go new file mode 100644 index 0000000000..87b2641a6a --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/basic_auth.go @@ -0,0 +1,32 @@ +package middleware + +import ( + "fmt" + "net/http" +) + +// BasicAuth implements a simple middleware handler for adding basic http auth to a route. +func BasicAuth(realm string, creds map[string]string) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user, pass, ok := r.BasicAuth() + if !ok { + basicAuthFailed(w, realm) + return + } + + credPass, credUserOk := creds[user] + if !credUserOk || pass != credPass { + basicAuthFailed(w, realm) + return + } + + next.ServeHTTP(w, r) + }) + } +} + +func basicAuthFailed(w http.ResponseWriter, realm string) { + w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm)) + w.WriteHeader(http.StatusUnauthorized) +} diff --git a/vendor/github.com/go-chi/chi/middleware/compress.go b/vendor/github.com/go-chi/chi/middleware/compress.go new file mode 100644 index 0000000000..2f40cc15af --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/compress.go @@ -0,0 +1,399 @@ +package middleware + +import ( + "bufio" + "compress/flate" + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "strings" + "sync" +) + +var defaultCompressibleContentTypes = []string{ + "text/html", + "text/css", + "text/plain", + "text/javascript", + "application/javascript", + "application/x-javascript", + "application/json", + "application/atom+xml", + "application/rss+xml", + "image/svg+xml", +} + +// Compress is a middleware that compresses response +// body of a given content types to a data format based +// on Accept-Encoding request header. It uses a given +// compression level. +// +// NOTE: make sure to set the Content-Type header on your response +// otherwise this middleware will not compress the response body. For ex, in +// your handler you should set w.Header().Set("Content-Type", http.DetectContentType(yourBody)) +// or set it manually. +// +// Passing a compression level of 5 is sensible value +func Compress(level int, types ...string) func(next http.Handler) http.Handler { + compressor := NewCompressor(level, types...) + return compressor.Handler +} + +// Compressor represents a set of encoding configurations. +type Compressor struct { + level int // The compression level. + // The mapping of encoder names to encoder functions. + encoders map[string]EncoderFunc + // The mapping of pooled encoders to pools. + pooledEncoders map[string]*sync.Pool + // The set of content types allowed to be compressed. + allowedTypes map[string]struct{} + allowedWildcards map[string]struct{} + // The list of encoders in order of decreasing precedence. + encodingPrecedence []string +} + +// NewCompressor creates a new Compressor that will handle encoding responses. +// +// The level should be one of the ones defined in the flate package. +// The types are the content types that are allowed to be compressed. +func NewCompressor(level int, types ...string) *Compressor { + // If types are provided, set those as the allowed types. If none are + // provided, use the default list. + allowedTypes := make(map[string]struct{}) + allowedWildcards := make(map[string]struct{}) + if len(types) > 0 { + for _, t := range types { + if strings.Contains(strings.TrimSuffix(t, "/*"), "*") { + panic(fmt.Sprintf("middleware/compress: Unsupported content-type wildcard pattern '%s'. Only '/*' supported", t)) + } + if strings.HasSuffix(t, "/*") { + allowedWildcards[strings.TrimSuffix(t, "/*")] = struct{}{} + } else { + allowedTypes[t] = struct{}{} + } + } + } else { + for _, t := range defaultCompressibleContentTypes { + allowedTypes[t] = struct{}{} + } + } + + c := &Compressor{ + level: level, + encoders: make(map[string]EncoderFunc), + pooledEncoders: make(map[string]*sync.Pool), + allowedTypes: allowedTypes, + allowedWildcards: allowedWildcards, + } + + // Set the default encoders. The precedence order uses the reverse + // ordering that the encoders were added. This means adding new encoders + // will move them to the front of the order. + // + // TODO: + // lzma: Opera. + // sdch: Chrome, Android. Gzip output + dictionary header. + // br: Brotli, see https://github.com/go-chi/chi/pull/326 + + // HTTP 1.1 "deflate" (RFC 2616) stands for DEFLATE data (RFC 1951) + // wrapped with zlib (RFC 1950). The zlib wrapper uses Adler-32 + // checksum compared to CRC-32 used in "gzip" and thus is faster. + // + // But.. some old browsers (MSIE, Safari 5.1) incorrectly expect + // raw DEFLATE data only, without the mentioned zlib wrapper. + // Because of this major confusion, most modern browsers try it + // both ways, first looking for zlib headers. + // Quote by Mark Adler: http://stackoverflow.com/a/9186091/385548 + // + // The list of browsers having problems is quite big, see: + // http://zoompf.com/blog/2012/02/lose-the-wait-http-compression + // https://web.archive.org/web/20120321182910/http://www.vervestudios.co/projects/compression-tests/results + // + // That's why we prefer gzip over deflate. It's just more reliable + // and not significantly slower than gzip. + c.SetEncoder("deflate", encoderDeflate) + + // TODO: Exception for old MSIE browsers that can't handle non-HTML? + // https://zoompf.com/blog/2012/02/lose-the-wait-http-compression + c.SetEncoder("gzip", encoderGzip) + + // NOTE: Not implemented, intentionally: + // case "compress": // LZW. Deprecated. + // case "bzip2": // Too slow on-the-fly. + // case "zopfli": // Too slow on-the-fly. + // case "xz": // Too slow on-the-fly. + return c +} + +// SetEncoder can be used to set the implementation of a compression algorithm. +// +// The encoding should be a standardised identifier. See: +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding +// +// For example, add the Brotli algortithm: +// +// import brotli_enc "gopkg.in/kothar/brotli-go.v0/enc" +// +// compressor := middleware.NewCompressor(5, "text/html") +// compressor.SetEncoder("br", func(w http.ResponseWriter, level int) io.Writer { +// params := brotli_enc.NewBrotliParams() +// params.SetQuality(level) +// return brotli_enc.NewBrotliWriter(params, w) +// }) +func (c *Compressor) SetEncoder(encoding string, fn EncoderFunc) { + encoding = strings.ToLower(encoding) + if encoding == "" { + panic("the encoding can not be empty") + } + if fn == nil { + panic("attempted to set a nil encoder function") + } + + // If we are adding a new encoder that is already registered, we have to + // clear that one out first. + if _, ok := c.pooledEncoders[encoding]; ok { + delete(c.pooledEncoders, encoding) + } + if _, ok := c.encoders[encoding]; ok { + delete(c.encoders, encoding) + } + + // If the encoder supports Resetting (IoReseterWriter), then it can be pooled. + encoder := fn(ioutil.Discard, c.level) + if encoder != nil { + if _, ok := encoder.(ioResetterWriter); ok { + pool := &sync.Pool{ + New: func() interface{} { + return fn(ioutil.Discard, c.level) + }, + } + c.pooledEncoders[encoding] = pool + } + } + // If the encoder is not in the pooledEncoders, add it to the normal encoders. + if _, ok := c.pooledEncoders[encoding]; !ok { + c.encoders[encoding] = fn + } + + for i, v := range c.encodingPrecedence { + if v == encoding { + c.encodingPrecedence = append(c.encodingPrecedence[:i], c.encodingPrecedence[i+1:]...) + } + } + + c.encodingPrecedence = append([]string{encoding}, c.encodingPrecedence...) +} + +// Handler returns a new middleware that will compress the response based on the +// current Compressor. +func (c *Compressor) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + encoder, encoding, cleanup := c.selectEncoder(r.Header, w) + + cw := &compressResponseWriter{ + ResponseWriter: w, + w: w, + contentTypes: c.allowedTypes, + contentWildcards: c.allowedWildcards, + encoding: encoding, + compressable: false, // determined in post-handler + } + if encoder != nil { + cw.w = encoder + } + // Re-add the encoder to the pool if applicable. + defer cleanup() + defer cw.Close() + + next.ServeHTTP(cw, r) + }) +} + +// selectEncoder returns the encoder, the name of the encoder, and a closer function. +func (c *Compressor) selectEncoder(h http.Header, w io.Writer) (io.Writer, string, func()) { + header := h.Get("Accept-Encoding") + + // Parse the names of all accepted algorithms from the header. + accepted := strings.Split(strings.ToLower(header), ",") + + // Find supported encoder by accepted list by precedence + for _, name := range c.encodingPrecedence { + if matchAcceptEncoding(accepted, name) { + if pool, ok := c.pooledEncoders[name]; ok { + encoder := pool.Get().(ioResetterWriter) + cleanup := func() { + pool.Put(encoder) + } + encoder.Reset(w) + return encoder, name, cleanup + + } + if fn, ok := c.encoders[name]; ok { + return fn(w, c.level), name, func() {} + } + } + + } + + // No encoder found to match the accepted encoding + return nil, "", func() {} +} + +func matchAcceptEncoding(accepted []string, encoding string) bool { + for _, v := range accepted { + if strings.Contains(v, encoding) { + return true + } + } + return false +} + +// An EncoderFunc is a function that wraps the provided io.Writer with a +// streaming compression algorithm and returns it. +// +// In case of failure, the function should return nil. +type EncoderFunc func(w io.Writer, level int) io.Writer + +// Interface for types that allow resetting io.Writers. +type ioResetterWriter interface { + io.Writer + Reset(w io.Writer) +} + +type compressResponseWriter struct { + http.ResponseWriter + + // The streaming encoder writer to be used if there is one. Otherwise, + // this is just the normal writer. + w io.Writer + encoding string + contentTypes map[string]struct{} + contentWildcards map[string]struct{} + wroteHeader bool + compressable bool +} + +func (cw *compressResponseWriter) isCompressable() bool { + // Parse the first part of the Content-Type response header. + contentType := cw.Header().Get("Content-Type") + if idx := strings.Index(contentType, ";"); idx >= 0 { + contentType = contentType[0:idx] + } + + // Is the content type compressable? + if _, ok := cw.contentTypes[contentType]; ok { + return true + } + if idx := strings.Index(contentType, "/"); idx > 0 { + contentType = contentType[0:idx] + _, ok := cw.contentWildcards[contentType] + return ok + } + return false +} + +func (cw *compressResponseWriter) WriteHeader(code int) { + if cw.wroteHeader { + cw.ResponseWriter.WriteHeader(code) // Allow multiple calls to propagate. + return + } + cw.wroteHeader = true + defer cw.ResponseWriter.WriteHeader(code) + + // Already compressed data? + if cw.Header().Get("Content-Encoding") != "" { + return + } + + if !cw.isCompressable() { + cw.compressable = false + return + } + + if cw.encoding != "" { + cw.compressable = true + cw.Header().Set("Content-Encoding", cw.encoding) + cw.Header().Set("Vary", "Accept-Encoding") + + // The content-length after compression is unknown + cw.Header().Del("Content-Length") + } +} + +func (cw *compressResponseWriter) Write(p []byte) (int, error) { + if !cw.wroteHeader { + cw.WriteHeader(http.StatusOK) + } + + return cw.writer().Write(p) +} + +func (cw *compressResponseWriter) writer() io.Writer { + if cw.compressable { + return cw.w + } else { + return cw.ResponseWriter + } +} + +type compressFlusher interface { + Flush() error +} + +func (cw *compressResponseWriter) Flush() { + if f, ok := cw.writer().(http.Flusher); ok { + f.Flush() + } + // If the underlying writer has a compression flush signature, + // call this Flush() method instead + if f, ok := cw.writer().(compressFlusher); ok { + f.Flush() + + // Also flush the underlying response writer + if f, ok := cw.ResponseWriter.(http.Flusher); ok { + f.Flush() + } + } +} + +func (cw *compressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + if hj, ok := cw.writer().(http.Hijacker); ok { + return hj.Hijack() + } + return nil, nil, errors.New("chi/middleware: http.Hijacker is unavailable on the writer") +} + +func (cw *compressResponseWriter) Push(target string, opts *http.PushOptions) error { + if ps, ok := cw.writer().(http.Pusher); ok { + return ps.Push(target, opts) + } + return errors.New("chi/middleware: http.Pusher is unavailable on the writer") +} + +func (cw *compressResponseWriter) Close() error { + if c, ok := cw.writer().(io.WriteCloser); ok { + return c.Close() + } + return errors.New("chi/middleware: io.WriteCloser is unavailable on the writer") +} + +func encoderGzip(w io.Writer, level int) io.Writer { + gw, err := gzip.NewWriterLevel(w, level) + if err != nil { + return nil + } + return gw +} + +func encoderDeflate(w io.Writer, level int) io.Writer { + dw, err := flate.NewWriter(w, level) + if err != nil { + return nil + } + return dw +} diff --git a/vendor/github.com/go-chi/chi/middleware/content_charset.go b/vendor/github.com/go-chi/chi/middleware/content_charset.go new file mode 100644 index 0000000000..07b5ce6f66 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/content_charset.go @@ -0,0 +1,51 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// ContentCharset generates a handler that writes a 415 Unsupported Media Type response if none of the charsets match. +// An empty charset will allow requests with no Content-Type header or no specified charset. +func ContentCharset(charsets ...string) func(next http.Handler) http.Handler { + for i, c := range charsets { + charsets[i] = strings.ToLower(c) + } + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !contentEncoding(r.Header.Get("Content-Type"), charsets...) { + w.WriteHeader(http.StatusUnsupportedMediaType) + return + } + + next.ServeHTTP(w, r) + }) + } +} + +// Check the content encoding against a list of acceptable values. +func contentEncoding(ce string, charsets ...string) bool { + _, ce = split(strings.ToLower(ce), ";") + _, ce = split(ce, "charset=") + ce, _ = split(ce, ";") + for _, c := range charsets { + if ce == c { + return true + } + } + + return false +} + +// Split a string in two parts, cleaning any whitespace. +func split(str, sep string) (string, string) { + var a, b string + var parts = strings.SplitN(str, sep, 2) + a = strings.TrimSpace(parts[0]) + if len(parts) == 2 { + b = strings.TrimSpace(parts[1]) + } + + return a, b +} diff --git a/vendor/github.com/go-chi/chi/middleware/content_encoding.go b/vendor/github.com/go-chi/chi/middleware/content_encoding.go new file mode 100644 index 0000000000..e0b9ccc08a --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/content_encoding.go @@ -0,0 +1,34 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// AllowContentEncoding enforces a whitelist of request Content-Encoding otherwise responds +// with a 415 Unsupported Media Type status. +func AllowContentEncoding(contentEncoding ...string) func(next http.Handler) http.Handler { + allowedEncodings := make(map[string]struct{}, len(contentEncoding)) + for _, encoding := range contentEncoding { + allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))] = struct{}{} + } + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + requestEncodings := r.Header["Content-Encoding"] + // skip check for empty content body or no Content-Encoding + if r.ContentLength == 0 { + next.ServeHTTP(w, r) + return + } + // All encodings in the request must be allowed + for _, encoding := range requestEncodings { + if _, ok := allowedEncodings[strings.TrimSpace(strings.ToLower(encoding))]; !ok { + w.WriteHeader(http.StatusUnsupportedMediaType) + return + } + } + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} diff --git a/vendor/github.com/go-chi/chi/middleware/content_type.go b/vendor/github.com/go-chi/chi/middleware/content_type.go new file mode 100644 index 0000000000..ee4957874f --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/content_type.go @@ -0,0 +1,51 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// SetHeader is a convenience handler to set a response header key/value +func SetHeader(key, value string) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(key, value) + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} + +// AllowContentType enforces a whitelist of request Content-Types otherwise responds +// with a 415 Unsupported Media Type status. +func AllowContentType(contentTypes ...string) func(next http.Handler) http.Handler { + cT := []string{} + for _, t := range contentTypes { + cT = append(cT, strings.ToLower(t)) + } + + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if r.ContentLength == 0 { + // skip check for empty content body + next.ServeHTTP(w, r) + return + } + + s := strings.ToLower(strings.TrimSpace(r.Header.Get("Content-Type"))) + if i := strings.Index(s, ";"); i > -1 { + s = s[0:i] + } + + for _, t := range cT { + if t == s { + next.ServeHTTP(w, r) + return + } + } + + w.WriteHeader(http.StatusUnsupportedMediaType) + } + return http.HandlerFunc(fn) + } +} diff --git a/vendor/github.com/go-chi/chi/middleware/get_head.go b/vendor/github.com/go-chi/chi/middleware/get_head.go new file mode 100644 index 0000000000..86068a96db --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/get_head.go @@ -0,0 +1,39 @@ +package middleware + +import ( + "net/http" + + "github.com/go-chi/chi" +) + +// GetHead automatically route undefined HEAD requests to GET handlers. +func GetHead(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "HEAD" { + rctx := chi.RouteContext(r.Context()) + routePath := rctx.RoutePath + if routePath == "" { + if r.URL.RawPath != "" { + routePath = r.URL.RawPath + } else { + routePath = r.URL.Path + } + } + + // Temporary routing context to look-ahead before routing the request + tctx := chi.NewRouteContext() + + // Attempt to find a HEAD handler for the routing path, if not found, traverse + // the router as through its a GET route, but proceed with the request + // with the HEAD method. + if !rctx.Routes.Match(tctx, "HEAD", routePath) { + rctx.RouteMethod = "GET" + rctx.RoutePath = routePath + next.ServeHTTP(w, r) + return + } + } + + next.ServeHTTP(w, r) + }) +} diff --git a/vendor/github.com/go-chi/chi/middleware/heartbeat.go b/vendor/github.com/go-chi/chi/middleware/heartbeat.go new file mode 100644 index 0000000000..fe822fb536 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/heartbeat.go @@ -0,0 +1,26 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// Heartbeat endpoint middleware useful to setting up a path like +// `/ping` that load balancers or uptime testing external services +// can make a request before hitting any routes. It's also convenient +// to place this above ACL middlewares as well. +func Heartbeat(endpoint string) func(http.Handler) http.Handler { + f := func(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if r.Method == "GET" && strings.EqualFold(r.URL.Path, endpoint) { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(http.StatusOK) + w.Write([]byte(".")) + return + } + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } + return f +} diff --git a/vendor/github.com/go-chi/chi/middleware/logger.go b/vendor/github.com/go-chi/chi/middleware/logger.go new file mode 100644 index 0000000000..158a6a3905 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/logger.go @@ -0,0 +1,155 @@ +package middleware + +import ( + "bytes" + "context" + "log" + "net/http" + "os" + "time" +) + +var ( + // LogEntryCtxKey is the context.Context key to store the request log entry. + LogEntryCtxKey = &contextKey{"LogEntry"} + + // DefaultLogger is called by the Logger middleware handler to log each request. + // Its made a package-level variable so that it can be reconfigured for custom + // logging configurations. + DefaultLogger = RequestLogger(&DefaultLogFormatter{Logger: log.New(os.Stdout, "", log.LstdFlags), NoColor: false}) +) + +// Logger is a middleware that logs the start and end of each request, along +// with some useful data about what was requested, what the response status was, +// and how long it took to return. When standard output is a TTY, Logger will +// print in color, otherwise it will print in black and white. Logger prints a +// request ID if one is provided. +// +// Alternatively, look at https://github.com/goware/httplog for a more in-depth +// http logger with structured logging support. +func Logger(next http.Handler) http.Handler { + return DefaultLogger(next) +} + +// RequestLogger returns a logger handler using a custom LogFormatter. +func RequestLogger(f LogFormatter) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + entry := f.NewLogEntry(r) + ww := NewWrapResponseWriter(w, r.ProtoMajor) + + t1 := time.Now() + defer func() { + entry.Write(ww.Status(), ww.BytesWritten(), ww.Header(), time.Since(t1), nil) + }() + + next.ServeHTTP(ww, WithLogEntry(r, entry)) + } + return http.HandlerFunc(fn) + } +} + +// LogFormatter initiates the beginning of a new LogEntry per request. +// See DefaultLogFormatter for an example implementation. +type LogFormatter interface { + NewLogEntry(r *http.Request) LogEntry +} + +// LogEntry records the final log when a request completes. +// See defaultLogEntry for an example implementation. +type LogEntry interface { + Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) + Panic(v interface{}, stack []byte) +} + +// GetLogEntry returns the in-context LogEntry for a request. +func GetLogEntry(r *http.Request) LogEntry { + entry, _ := r.Context().Value(LogEntryCtxKey).(LogEntry) + return entry +} + +// WithLogEntry sets the in-context LogEntry for a request. +func WithLogEntry(r *http.Request, entry LogEntry) *http.Request { + r = r.WithContext(context.WithValue(r.Context(), LogEntryCtxKey, entry)) + return r +} + +// LoggerInterface accepts printing to stdlib logger or compatible logger. +type LoggerInterface interface { + Print(v ...interface{}) +} + +// DefaultLogFormatter is a simple logger that implements a LogFormatter. +type DefaultLogFormatter struct { + Logger LoggerInterface + NoColor bool +} + +// NewLogEntry creates a new LogEntry for the request. +func (l *DefaultLogFormatter) NewLogEntry(r *http.Request) LogEntry { + useColor := !l.NoColor + entry := &defaultLogEntry{ + DefaultLogFormatter: l, + request: r, + buf: &bytes.Buffer{}, + useColor: useColor, + } + + reqID := GetReqID(r.Context()) + if reqID != "" { + cW(entry.buf, useColor, nYellow, "[%s] ", reqID) + } + cW(entry.buf, useColor, nCyan, "\"") + cW(entry.buf, useColor, bMagenta, "%s ", r.Method) + + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + cW(entry.buf, useColor, nCyan, "%s://%s%s %s\" ", scheme, r.Host, r.RequestURI, r.Proto) + + entry.buf.WriteString("from ") + entry.buf.WriteString(r.RemoteAddr) + entry.buf.WriteString(" - ") + + return entry +} + +type defaultLogEntry struct { + *DefaultLogFormatter + request *http.Request + buf *bytes.Buffer + useColor bool +} + +func (l *defaultLogEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{}) { + switch { + case status < 200: + cW(l.buf, l.useColor, bBlue, "%03d", status) + case status < 300: + cW(l.buf, l.useColor, bGreen, "%03d", status) + case status < 400: + cW(l.buf, l.useColor, bCyan, "%03d", status) + case status < 500: + cW(l.buf, l.useColor, bYellow, "%03d", status) + default: + cW(l.buf, l.useColor, bRed, "%03d", status) + } + + cW(l.buf, l.useColor, bBlue, " %dB", bytes) + + l.buf.WriteString(" in ") + if elapsed < 500*time.Millisecond { + cW(l.buf, l.useColor, nGreen, "%s", elapsed) + } else if elapsed < 5*time.Second { + cW(l.buf, l.useColor, nYellow, "%s", elapsed) + } else { + cW(l.buf, l.useColor, nRed, "%s", elapsed) + } + + l.Logger.Print(l.buf.String()) +} + +func (l *defaultLogEntry) Panic(v interface{}, stack []byte) { + PrintPrettyStack(v) +} diff --git a/vendor/github.com/go-chi/chi/middleware/middleware.go b/vendor/github.com/go-chi/chi/middleware/middleware.go new file mode 100644 index 0000000000..cc371e00a8 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/middleware.go @@ -0,0 +1,23 @@ +package middleware + +import "net/http" + +// New will create a new middleware handler from a http.Handler. +func New(h http.Handler) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h.ServeHTTP(w, r) + }) + } +} + +// contextKey is a value for use with context.WithValue. It's used as +// a pointer so it fits in an interface{} without allocation. This technique +// for defining context keys was copied from Go 1.7's new use of context in net/http. +type contextKey struct { + name string +} + +func (k *contextKey) String() string { + return "chi/middleware context value " + k.name +} diff --git a/vendor/github.com/go-chi/chi/middleware/nocache.go b/vendor/github.com/go-chi/chi/middleware/nocache.go new file mode 100644 index 0000000000..2412829e1b --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/nocache.go @@ -0,0 +1,58 @@ +package middleware + +// Ported from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "net/http" + "time" +) + +// Unix epoch time +var epoch = time.Unix(0, 0).Format(time.RFC1123) + +// Taken from https://github.com/mytrile/nocache +var noCacheHeaders = map[string]string{ + "Expires": epoch, + "Cache-Control": "no-cache, no-store, no-transform, must-revalidate, private, max-age=0", + "Pragma": "no-cache", + "X-Accel-Expires": "0", +} + +var etagHeaders = []string{ + "ETag", + "If-Modified-Since", + "If-Match", + "If-None-Match", + "If-Range", + "If-Unmodified-Since", +} + +// NoCache is a simple piece of middleware that sets a number of HTTP headers to prevent +// a router (or subrouter) from being cached by an upstream proxy and/or client. +// +// As per http://wiki.nginx.org/HttpProxyModule - NoCache sets: +// Expires: Thu, 01 Jan 1970 00:00:00 UTC +// Cache-Control: no-cache, private, max-age=0 +// X-Accel-Expires: 0 +// Pragma: no-cache (for HTTP/1.0 proxies/clients) +func NoCache(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + + // Delete any ETag headers that may have been set + for _, v := range etagHeaders { + if r.Header.Get(v) != "" { + r.Header.Del(v) + } + } + + // Set our NoCache headers + for k, v := range noCacheHeaders { + w.Header().Set(k, v) + } + + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} diff --git a/vendor/github.com/go-chi/chi/middleware/profiler.go b/vendor/github.com/go-chi/chi/middleware/profiler.go new file mode 100644 index 0000000000..1d44b8259a --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/profiler.go @@ -0,0 +1,55 @@ +package middleware + +import ( + "expvar" + "fmt" + "net/http" + "net/http/pprof" + + "github.com/go-chi/chi" +) + +// Profiler is a convenient subrouter used for mounting net/http/pprof. ie. +// +// func MyService() http.Handler { +// r := chi.NewRouter() +// // ..middlewares +// r.Mount("/debug", middleware.Profiler()) +// // ..routes +// return r +// } +func Profiler() http.Handler { + r := chi.NewRouter() + r.Use(NoCache) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, r.RequestURI+"/pprof/", 301) + }) + r.HandleFunc("/pprof", func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, r.RequestURI+"/", 301) + }) + + r.HandleFunc("/pprof/*", pprof.Index) + r.HandleFunc("/pprof/cmdline", pprof.Cmdline) + r.HandleFunc("/pprof/profile", pprof.Profile) + r.HandleFunc("/pprof/symbol", pprof.Symbol) + r.HandleFunc("/pprof/trace", pprof.Trace) + r.HandleFunc("/vars", expVars) + + return r +} + +// Replicated from expvar.go as not public. +func expVars(w http.ResponseWriter, r *http.Request) { + first := true + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, "{\n") + expvar.Do(func(kv expvar.KeyValue) { + if !first { + fmt.Fprintf(w, ",\n") + } + first = false + fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) + }) + fmt.Fprintf(w, "\n}\n") +} diff --git a/vendor/github.com/go-chi/chi/middleware/realip.go b/vendor/github.com/go-chi/chi/middleware/realip.go new file mode 100644 index 0000000000..72db6ca9f5 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/realip.go @@ -0,0 +1,54 @@ +package middleware + +// Ported from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "net/http" + "strings" +) + +var xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") +var xRealIP = http.CanonicalHeaderKey("X-Real-IP") + +// RealIP is a middleware that sets a http.Request's RemoteAddr to the results +// of parsing either the X-Forwarded-For header or the X-Real-IP header (in that +// order). +// +// This middleware should be inserted fairly early in the middleware stack to +// ensure that subsequent layers (e.g., request loggers) which examine the +// RemoteAddr will see the intended value. +// +// You should only use this middleware if you can trust the headers passed to +// you (in particular, the two headers this middleware uses), for example +// because you have placed a reverse proxy like HAProxy or nginx in front of +// chi. If your reverse proxies are configured to pass along arbitrary header +// values from the client, or if you use this middleware without a reverse +// proxy, malicious clients will be able to make you very sad (or, depending on +// how you're using RemoteAddr, vulnerable to an attack of some sort). +func RealIP(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if rip := realIP(r); rip != "" { + r.RemoteAddr = rip + } + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +func realIP(r *http.Request) string { + var ip string + + if xrip := r.Header.Get(xRealIP); xrip != "" { + ip = xrip + } else if xff := r.Header.Get(xForwardedFor); xff != "" { + i := strings.Index(xff, ", ") + if i == -1 { + i = len(xff) + } + ip = xff[:i] + } + + return ip +} diff --git a/vendor/github.com/go-chi/chi/middleware/recoverer.go b/vendor/github.com/go-chi/chi/middleware/recoverer.go new file mode 100644 index 0000000000..785b18c52b --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/recoverer.go @@ -0,0 +1,192 @@ +package middleware + +// The original work was derived from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "bytes" + "errors" + "fmt" + "net/http" + "os" + "runtime/debug" + "strings" +) + +// Recoverer is a middleware that recovers from panics, logs the panic (and a +// backtrace), and returns a HTTP 500 (Internal Server Error) status if +// possible. Recoverer prints a request ID if one is provided. +// +// Alternatively, look at https://github.com/pressly/lg middleware pkgs. +func Recoverer(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + defer func() { + if rvr := recover(); rvr != nil && rvr != http.ErrAbortHandler { + + logEntry := GetLogEntry(r) + if logEntry != nil { + logEntry.Panic(rvr, debug.Stack()) + } else { + PrintPrettyStack(rvr) + } + + w.WriteHeader(http.StatusInternalServerError) + } + }() + + next.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} + +func PrintPrettyStack(rvr interface{}) { + debugStack := debug.Stack() + s := prettyStack{} + out, err := s.parse(debugStack, rvr) + if err == nil { + os.Stderr.Write(out) + } else { + // print stdlib output as a fallback + os.Stderr.Write(debugStack) + } +} + +type prettyStack struct { +} + +func (s prettyStack) parse(debugStack []byte, rvr interface{}) ([]byte, error) { + var err error + useColor := true + buf := &bytes.Buffer{} + + cW(buf, false, bRed, "\n") + cW(buf, useColor, bCyan, " panic: ") + cW(buf, useColor, bBlue, "%v", rvr) + cW(buf, false, bWhite, "\n \n") + + // process debug stack info + stack := strings.Split(string(debugStack), "\n") + lines := []string{} + + // locate panic line, as we may have nested panics + for i := len(stack) - 1; i > 0; i-- { + lines = append(lines, stack[i]) + if strings.HasPrefix(stack[i], "panic(0x") { + lines = lines[0 : len(lines)-2] // remove boilerplate + break + } + } + + // reverse + for i := len(lines)/2 - 1; i >= 0; i-- { + opp := len(lines) - 1 - i + lines[i], lines[opp] = lines[opp], lines[i] + } + + // decorate + for i, line := range lines { + lines[i], err = s.decorateLine(line, useColor, i) + if err != nil { + return nil, err + } + } + + for _, l := range lines { + fmt.Fprintf(buf, "%s", l) + } + return buf.Bytes(), nil +} + +func (s prettyStack) decorateLine(line string, useColor bool, num int) (string, error) { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "\t") || strings.Contains(line, ".go:") { + return s.decorateSourceLine(line, useColor, num) + } else if strings.HasSuffix(line, ")") { + return s.decorateFuncCallLine(line, useColor, num) + } else { + if strings.HasPrefix(line, "\t") { + return strings.Replace(line, "\t", " ", 1), nil + } else { + return fmt.Sprintf(" %s\n", line), nil + } + } +} + +func (s prettyStack) decorateFuncCallLine(line string, useColor bool, num int) (string, error) { + idx := strings.LastIndex(line, "(") + if idx < 0 { + return "", errors.New("not a func call line") + } + + buf := &bytes.Buffer{} + pkg := line[0:idx] + // addr := line[idx:] + method := "" + + idx = strings.LastIndex(pkg, string(os.PathSeparator)) + if idx < 0 { + idx = strings.Index(pkg, ".") + method = pkg[idx:] + pkg = pkg[0:idx] + } else { + method = pkg[idx+1:] + pkg = pkg[0 : idx+1] + idx = strings.Index(method, ".") + pkg += method[0:idx] + method = method[idx:] + } + pkgColor := nYellow + methodColor := bGreen + + if num == 0 { + cW(buf, useColor, bRed, " -> ") + pkgColor = bMagenta + methodColor = bRed + } else { + cW(buf, useColor, bWhite, " ") + } + cW(buf, useColor, pkgColor, "%s", pkg) + cW(buf, useColor, methodColor, "%s\n", method) + // cW(buf, useColor, nBlack, "%s", addr) + return buf.String(), nil +} + +func (s prettyStack) decorateSourceLine(line string, useColor bool, num int) (string, error) { + idx := strings.LastIndex(line, ".go:") + if idx < 0 { + return "", errors.New("not a source line") + } + + buf := &bytes.Buffer{} + path := line[0 : idx+3] + lineno := line[idx+3:] + + idx = strings.LastIndex(path, string(os.PathSeparator)) + dir := path[0 : idx+1] + file := path[idx+1:] + + idx = strings.Index(lineno, " ") + if idx > 0 { + lineno = lineno[0:idx] + } + fileColor := bCyan + lineColor := bGreen + + if num == 1 { + cW(buf, useColor, bRed, " -> ") + fileColor = bRed + lineColor = bMagenta + } else { + cW(buf, false, bWhite, " ") + } + cW(buf, useColor, bWhite, "%s", dir) + cW(buf, useColor, fileColor, "%s", file) + cW(buf, useColor, lineColor, "%s", lineno) + if num == 1 { + cW(buf, false, bWhite, "\n") + } + cW(buf, false, bWhite, "\n") + + return buf.String(), nil +} diff --git a/vendor/github.com/go-chi/chi/middleware/request_id.go b/vendor/github.com/go-chi/chi/middleware/request_id.go new file mode 100644 index 0000000000..4903ecc214 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/request_id.go @@ -0,0 +1,96 @@ +package middleware + +// Ported from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" + "net/http" + "os" + "strings" + "sync/atomic" +) + +// Key to use when setting the request ID. +type ctxKeyRequestID int + +// RequestIDKey is the key that holds the unique request ID in a request context. +const RequestIDKey ctxKeyRequestID = 0 + +// RequestIDHeader is the name of the HTTP Header which contains the request id. +// Exported so that it can be changed by developers +var RequestIDHeader = "X-Request-Id" + +var prefix string +var reqid uint64 + +// A quick note on the statistics here: we're trying to calculate the chance that +// two randomly generated base62 prefixes will collide. We use the formula from +// http://en.wikipedia.org/wiki/Birthday_problem +// +// P[m, n] \approx 1 - e^{-m^2/2n} +// +// We ballpark an upper bound for $m$ by imagining (for whatever reason) a server +// that restarts every second over 10 years, for $m = 86400 * 365 * 10 = 315360000$ +// +// For a $k$ character base-62 identifier, we have $n(k) = 62^k$ +// +// Plugging this in, we find $P[m, n(10)] \approx 5.75%$, which is good enough for +// our purposes, and is surely more than anyone would ever need in practice -- a +// process that is rebooted a handful of times a day for a hundred years has less +// than a millionth of a percent chance of generating two colliding IDs. + +func init() { + hostname, err := os.Hostname() + if hostname == "" || err != nil { + hostname = "localhost" + } + var buf [12]byte + var b64 string + for len(b64) < 10 { + rand.Read(buf[:]) + b64 = base64.StdEncoding.EncodeToString(buf[:]) + b64 = strings.NewReplacer("+", "", "/", "").Replace(b64) + } + + prefix = fmt.Sprintf("%s/%s", hostname, b64[0:10]) +} + +// RequestID is a middleware that injects a request ID into the context of each +// request. A request ID is a string of the form "host.example.com/random-0001", +// where "random" is a base62 random string that uniquely identifies this go +// process, and where the last number is an atomically incremented request +// counter. +func RequestID(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + requestID := r.Header.Get(RequestIDHeader) + if requestID == "" { + myid := atomic.AddUint64(&reqid, 1) + requestID = fmt.Sprintf("%s-%06d", prefix, myid) + } + ctx = context.WithValue(ctx, RequestIDKey, requestID) + next.ServeHTTP(w, r.WithContext(ctx)) + } + return http.HandlerFunc(fn) +} + +// GetReqID returns a request ID from the given context if one is present. +// Returns the empty string if a request ID cannot be found. +func GetReqID(ctx context.Context) string { + if ctx == nil { + return "" + } + if reqID, ok := ctx.Value(RequestIDKey).(string); ok { + return reqID + } + return "" +} + +// NextRequestID generates the next request ID in the sequence. +func NextRequestID() uint64 { + return atomic.AddUint64(&reqid, 1) +} diff --git a/vendor/github.com/go-chi/chi/middleware/route_headers.go b/vendor/github.com/go-chi/chi/middleware/route_headers.go new file mode 100644 index 0000000000..7ee30c8773 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/route_headers.go @@ -0,0 +1,160 @@ +package middleware + +import ( + "net/http" + "strings" +) + +// RouteHeaders is a neat little header-based router that allows you to direct +// the flow of a request through a middleware stack based on a request header. +// +// For example, lets say you'd like to setup multiple routers depending on the +// request Host header, you could then do something as so: +// +// r := chi.NewRouter() +// rSubdomain := chi.NewRouter() +// +// r.Use(middleware.RouteHeaders(). +// Route("Host", "example.com", middleware.New(r)). +// Route("Host", "*.example.com", middleware.New(rSubdomain)). +// Handler) +// +// r.Get("/", h) +// rSubdomain.Get("/", h2) +// +// +// Another example, imagine you want to setup multiple CORS handlers, where for +// your origin servers you allow authorized requests, but for third-party public +// requests, authorization is disabled. +// +// r := chi.NewRouter() +// +// r.Use(middleware.RouteHeaders(). +// Route("Origin", "https://app.skyweaver.net", cors.Handler(cors.Options{ +// AllowedOrigins: []string{"https://api.skyweaver.net"}, +// AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, +// AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"}, +// AllowCredentials: true, // <----------<<< allow credentials +// })). +// Route("Origin", "*", cors.Handler(cors.Options{ +// AllowedOrigins: []string{"*"}, +// AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, +// AllowedHeaders: []string{"Accept", "Content-Type"}, +// AllowCredentials: false, // <----------<<< do not allow credentials +// })). +// Handler) +// +func RouteHeaders() HeaderRouter { + return HeaderRouter{} +} + +type HeaderRouter map[string][]HeaderRoute + +func (hr HeaderRouter) Route(header string, match string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter { + header = strings.ToLower(header) + k := hr[header] + if k == nil { + hr[header] = []HeaderRoute{} + } + hr[header] = append(hr[header], HeaderRoute{MatchOne: NewPattern(match), Middleware: middlewareHandler}) + return hr +} + +func (hr HeaderRouter) RouteAny(header string, match []string, middlewareHandler func(next http.Handler) http.Handler) HeaderRouter { + header = strings.ToLower(header) + k := hr[header] + if k == nil { + hr[header] = []HeaderRoute{} + } + patterns := []Pattern{} + for _, m := range match { + patterns = append(patterns, NewPattern(m)) + } + hr[header] = append(hr[header], HeaderRoute{MatchAny: patterns, Middleware: middlewareHandler}) + return hr +} + +func (hr HeaderRouter) RouteDefault(handler func(next http.Handler) http.Handler) HeaderRouter { + hr["*"] = []HeaderRoute{{Middleware: handler}} + return hr +} + +func (hr HeaderRouter) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if len(hr) == 0 { + // skip if no routes set + next.ServeHTTP(w, r) + } + + // find first matching header route, and continue + for header, matchers := range hr { + headerValue := r.Header.Get(header) + if headerValue == "" { + continue + } + headerValue = strings.ToLower(headerValue) + for _, matcher := range matchers { + if matcher.IsMatch(headerValue) { + matcher.Middleware(next).ServeHTTP(w, r) + return + } + } + } + + // if no match, check for "*" default route + matcher, ok := hr["*"] + if !ok || matcher[0].Middleware == nil { + next.ServeHTTP(w, r) + return + } + matcher[0].Middleware(next).ServeHTTP(w, r) + }) +} + +type HeaderRoute struct { + MatchAny []Pattern + MatchOne Pattern + Middleware func(next http.Handler) http.Handler +} + +func (r HeaderRoute) IsMatch(value string) bool { + if len(r.MatchAny) > 0 { + for _, m := range r.MatchAny { + if m.Match(value) { + return true + } + } + } else if r.MatchOne.Match(value) { + return true + } + return false +} + +type Pattern struct { + prefix string + suffix string + wildcard bool +} + +func NewPattern(value string) Pattern { + p := Pattern{} + if i := strings.IndexByte(value, '*'); i >= 0 { + p.wildcard = true + p.prefix = value[0:i] + p.suffix = value[i+1:] + } else { + p.prefix = value + } + return p +} + +func (p Pattern) Match(v string) bool { + if !p.wildcard { + if p.prefix == v { + return true + } else { + return false + } + } + return len(v) >= len(p.prefix+p.suffix) && strings.HasPrefix(v, p.prefix) && strings.HasSuffix(v, p.suffix) +} diff --git a/vendor/github.com/go-chi/chi/middleware/strip.go b/vendor/github.com/go-chi/chi/middleware/strip.go new file mode 100644 index 0000000000..2b8b1842ab --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/strip.go @@ -0,0 +1,56 @@ +package middleware + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi" +) + +// StripSlashes is a middleware that will match request paths with a trailing +// slash, strip it from the path and continue routing through the mux, if a route +// matches, then it will serve the handler. +func StripSlashes(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + var path string + rctx := chi.RouteContext(r.Context()) + if rctx.RoutePath != "" { + path = rctx.RoutePath + } else { + path = r.URL.Path + } + if len(path) > 1 && path[len(path)-1] == '/' { + rctx.RoutePath = path[:len(path)-1] + } + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} + +// RedirectSlashes is a middleware that will match request paths with a trailing +// slash and redirect to the same path, less the trailing slash. +// +// NOTE: RedirectSlashes middleware is *incompatible* with http.FileServer, +// see https://github.com/go-chi/chi/issues/343 +func RedirectSlashes(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + var path string + rctx := chi.RouteContext(r.Context()) + if rctx.RoutePath != "" { + path = rctx.RoutePath + } else { + path = r.URL.Path + } + if len(path) > 1 && path[len(path)-1] == '/' { + if r.URL.RawQuery != "" { + path = fmt.Sprintf("%s?%s", path[:len(path)-1], r.URL.RawQuery) + } else { + path = path[:len(path)-1] + } + http.Redirect(w, r, path, 301) + return + } + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} diff --git a/vendor/github.com/go-chi/chi/middleware/terminal.go b/vendor/github.com/go-chi/chi/middleware/terminal.go new file mode 100644 index 0000000000..5ead7b9243 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/terminal.go @@ -0,0 +1,63 @@ +package middleware + +// Ported from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "fmt" + "io" + "os" +) + +var ( + // Normal colors + nBlack = []byte{'\033', '[', '3', '0', 'm'} + nRed = []byte{'\033', '[', '3', '1', 'm'} + nGreen = []byte{'\033', '[', '3', '2', 'm'} + nYellow = []byte{'\033', '[', '3', '3', 'm'} + nBlue = []byte{'\033', '[', '3', '4', 'm'} + nMagenta = []byte{'\033', '[', '3', '5', 'm'} + nCyan = []byte{'\033', '[', '3', '6', 'm'} + nWhite = []byte{'\033', '[', '3', '7', 'm'} + // Bright colors + bBlack = []byte{'\033', '[', '3', '0', ';', '1', 'm'} + bRed = []byte{'\033', '[', '3', '1', ';', '1', 'm'} + bGreen = []byte{'\033', '[', '3', '2', ';', '1', 'm'} + bYellow = []byte{'\033', '[', '3', '3', ';', '1', 'm'} + bBlue = []byte{'\033', '[', '3', '4', ';', '1', 'm'} + bMagenta = []byte{'\033', '[', '3', '5', ';', '1', 'm'} + bCyan = []byte{'\033', '[', '3', '6', ';', '1', 'm'} + bWhite = []byte{'\033', '[', '3', '7', ';', '1', 'm'} + + reset = []byte{'\033', '[', '0', 'm'} +) + +var IsTTY bool + +func init() { + // This is sort of cheating: if stdout is a character device, we assume + // that means it's a TTY. Unfortunately, there are many non-TTY + // character devices, but fortunately stdout is rarely set to any of + // them. + // + // We could solve this properly by pulling in a dependency on + // code.google.com/p/go.crypto/ssh/terminal, for instance, but as a + // heuristic for whether to print in color or in black-and-white, I'd + // really rather not. + fi, err := os.Stdout.Stat() + if err == nil { + m := os.ModeDevice | os.ModeCharDevice + IsTTY = fi.Mode()&m == m + } +} + +// colorWrite +func cW(w io.Writer, useColor bool, color []byte, s string, args ...interface{}) { + if IsTTY && useColor { + w.Write(color) + } + fmt.Fprintf(w, s, args...) + if IsTTY && useColor { + w.Write(reset) + } +} diff --git a/vendor/github.com/go-chi/chi/middleware/throttle.go b/vendor/github.com/go-chi/chi/middleware/throttle.go new file mode 100644 index 0000000000..fdedd3c127 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/throttle.go @@ -0,0 +1,132 @@ +package middleware + +import ( + "net/http" + "strconv" + "time" +) + +const ( + errCapacityExceeded = "Server capacity exceeded." + errTimedOut = "Timed out while waiting for a pending request to complete." + errContextCanceled = "Context was canceled." +) + +var ( + defaultBacklogTimeout = time.Second * 60 +) + +// ThrottleOpts represents a set of throttling options. +type ThrottleOpts struct { + Limit int + BacklogLimit int + BacklogTimeout time.Duration + RetryAfterFn func(ctxDone bool) time.Duration +} + +// Throttle is a middleware that limits number of currently processed requests +// at a time across all users. Note: Throttle is not a rate-limiter per user, +// instead it just puts a ceiling on the number of currentl in-flight requests +// being processed from the point from where the Throttle middleware is mounted. +func Throttle(limit int) func(http.Handler) http.Handler { + return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogTimeout: defaultBacklogTimeout}) +} + +// ThrottleBacklog is a middleware that limits number of currently processed +// requests at a time and provides a backlog for holding a finite number of +// pending requests. +func ThrottleBacklog(limit int, backlogLimit int, backlogTimeout time.Duration) func(http.Handler) http.Handler { + return ThrottleWithOpts(ThrottleOpts{Limit: limit, BacklogLimit: backlogLimit, BacklogTimeout: backlogTimeout}) +} + +// ThrottleWithOpts is a middleware that limits number of currently processed requests using passed ThrottleOpts. +func ThrottleWithOpts(opts ThrottleOpts) func(http.Handler) http.Handler { + if opts.Limit < 1 { + panic("chi/middleware: Throttle expects limit > 0") + } + + if opts.BacklogLimit < 0 { + panic("chi/middleware: Throttle expects backlogLimit to be positive") + } + + t := throttler{ + tokens: make(chan token, opts.Limit), + backlogTokens: make(chan token, opts.Limit+opts.BacklogLimit), + backlogTimeout: opts.BacklogTimeout, + retryAfterFn: opts.RetryAfterFn, + } + + // Filling tokens. + for i := 0; i < opts.Limit+opts.BacklogLimit; i++ { + if i < opts.Limit { + t.tokens <- token{} + } + t.backlogTokens <- token{} + } + + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + select { + + case <-ctx.Done(): + t.setRetryAfterHeaderIfNeeded(w, true) + http.Error(w, errContextCanceled, http.StatusServiceUnavailable) + return + + case btok := <-t.backlogTokens: + timer := time.NewTimer(t.backlogTimeout) + + defer func() { + t.backlogTokens <- btok + }() + + select { + case <-timer.C: + t.setRetryAfterHeaderIfNeeded(w, false) + http.Error(w, errTimedOut, http.StatusServiceUnavailable) + return + case <-ctx.Done(): + timer.Stop() + t.setRetryAfterHeaderIfNeeded(w, true) + http.Error(w, errContextCanceled, http.StatusServiceUnavailable) + return + case tok := <-t.tokens: + defer func() { + timer.Stop() + t.tokens <- tok + }() + next.ServeHTTP(w, r) + } + return + + default: + t.setRetryAfterHeaderIfNeeded(w, false) + http.Error(w, errCapacityExceeded, http.StatusServiceUnavailable) + return + } + } + + return http.HandlerFunc(fn) + } +} + +// token represents a request that is being processed. +type token struct{} + +// throttler limits number of currently processed requests at a time. +type throttler struct { + tokens chan token + backlogTokens chan token + backlogTimeout time.Duration + retryAfterFn func(ctxDone bool) time.Duration +} + +// setRetryAfterHeaderIfNeeded sets Retry-After HTTP header if corresponding retryAfterFn option of throttler is initialized. +func (t throttler) setRetryAfterHeaderIfNeeded(w http.ResponseWriter, ctxDone bool) { + if t.retryAfterFn == nil { + return + } + w.Header().Set("Retry-After", strconv.Itoa(int(t.retryAfterFn(ctxDone).Seconds()))) +} diff --git a/vendor/github.com/go-chi/chi/middleware/timeout.go b/vendor/github.com/go-chi/chi/middleware/timeout.go new file mode 100644 index 0000000000..8e373536cf --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/timeout.go @@ -0,0 +1,49 @@ +package middleware + +import ( + "context" + "net/http" + "time" +) + +// Timeout is a middleware that cancels ctx after a given timeout and return +// a 504 Gateway Timeout error to the client. +// +// It's required that you select the ctx.Done() channel to check for the signal +// if the context has reached its deadline and return, otherwise the timeout +// signal will be just ignored. +// +// ie. a route/handler may look like: +// +// r.Get("/long", func(w http.ResponseWriter, r *http.Request) { +// ctx := r.Context() +// processTime := time.Duration(rand.Intn(4)+1) * time.Second +// +// select { +// case <-ctx.Done(): +// return +// +// case <-time.After(processTime): +// // The above channel simulates some hard work. +// } +// +// w.Write([]byte("done")) +// }) +// +func Timeout(timeout time.Duration) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), timeout) + defer func() { + cancel() + if ctx.Err() == context.DeadlineExceeded { + w.WriteHeader(http.StatusGatewayTimeout) + } + }() + + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} diff --git a/vendor/github.com/go-chi/chi/middleware/url_format.go b/vendor/github.com/go-chi/chi/middleware/url_format.go new file mode 100644 index 0000000000..5749e4f32b --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/url_format.go @@ -0,0 +1,72 @@ +package middleware + +import ( + "context" + "net/http" + "strings" + + "github.com/go-chi/chi" +) + +var ( + // URLFormatCtxKey is the context.Context key to store the URL format data + // for a request. + URLFormatCtxKey = &contextKey{"URLFormat"} +) + +// URLFormat is a middleware that parses the url extension from a request path and stores it +// on the context as a string under the key `middleware.URLFormatCtxKey`. The middleware will +// trim the suffix from the routing path and continue routing. +// +// Routers should not include a url parameter for the suffix when using this middleware. +// +// Sample usage.. for url paths: `/articles/1`, `/articles/1.json` and `/articles/1.xml` +// +// func routes() http.Handler { +// r := chi.NewRouter() +// r.Use(middleware.URLFormat) +// +// r.Get("/articles/{id}", ListArticles) +// +// return r +// } +// +// func ListArticles(w http.ResponseWriter, r *http.Request) { +// urlFormat, _ := r.Context().Value(middleware.URLFormatCtxKey).(string) +// +// switch urlFormat { +// case "json": +// render.JSON(w, r, articles) +// case "xml:" +// render.XML(w, r, articles) +// default: +// render.JSON(w, r, articles) +// } +// } +// +func URLFormat(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + var format string + path := r.URL.Path + + if strings.Index(path, ".") > 0 { + base := strings.LastIndex(path, "/") + idx := strings.Index(path[base:], ".") + + if idx > 0 { + idx += base + format = path[idx+1:] + + rctx := chi.RouteContext(r.Context()) + rctx.RoutePath = path[:idx] + } + } + + r = r.WithContext(context.WithValue(ctx, URLFormatCtxKey, format)) + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} diff --git a/vendor/github.com/go-chi/chi/middleware/value.go b/vendor/github.com/go-chi/chi/middleware/value.go new file mode 100644 index 0000000000..fbbd0393fb --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/value.go @@ -0,0 +1,17 @@ +package middleware + +import ( + "context" + "net/http" +) + +// WithValue is a middleware that sets a given key/value in a context chain. +func WithValue(key interface{}, val interface{}) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + r = r.WithContext(context.WithValue(r.Context(), key, val)) + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} diff --git a/vendor/github.com/go-chi/chi/middleware/wrap_writer.go b/vendor/github.com/go-chi/chi/middleware/wrap_writer.go new file mode 100644 index 0000000000..382a523e48 --- /dev/null +++ b/vendor/github.com/go-chi/chi/middleware/wrap_writer.go @@ -0,0 +1,180 @@ +package middleware + +// The original work was derived from Goji's middleware, source: +// https://github.com/zenazn/goji/tree/master/web/middleware + +import ( + "bufio" + "io" + "net" + "net/http" +) + +// NewWrapResponseWriter wraps an http.ResponseWriter, returning a proxy that allows you to +// hook into various parts of the response process. +func NewWrapResponseWriter(w http.ResponseWriter, protoMajor int) WrapResponseWriter { + _, fl := w.(http.Flusher) + + bw := basicWriter{ResponseWriter: w} + + if protoMajor == 2 { + _, ps := w.(http.Pusher) + if fl && ps { + return &http2FancyWriter{bw} + } + } else { + _, hj := w.(http.Hijacker) + _, rf := w.(io.ReaderFrom) + if fl && hj && rf { + return &httpFancyWriter{bw} + } + } + if fl { + return &flushWriter{bw} + } + + return &bw +} + +// WrapResponseWriter is a proxy around an http.ResponseWriter that allows you to hook +// into various parts of the response process. +type WrapResponseWriter interface { + http.ResponseWriter + // Status returns the HTTP status of the request, or 0 if one has not + // yet been sent. + Status() int + // BytesWritten returns the total number of bytes sent to the client. + BytesWritten() int + // Tee causes the response body to be written to the given io.Writer in + // addition to proxying the writes through. Only one io.Writer can be + // tee'd to at once: setting a second one will overwrite the first. + // Writes will be sent to the proxy before being written to this + // io.Writer. It is illegal for the tee'd writer to be modified + // concurrently with writes. + Tee(io.Writer) + // Unwrap returns the original proxied target. + Unwrap() http.ResponseWriter +} + +// basicWriter wraps a http.ResponseWriter that implements the minimal +// http.ResponseWriter interface. +type basicWriter struct { + http.ResponseWriter + wroteHeader bool + code int + bytes int + tee io.Writer +} + +func (b *basicWriter) WriteHeader(code int) { + if !b.wroteHeader { + b.code = code + b.wroteHeader = true + b.ResponseWriter.WriteHeader(code) + } +} + +func (b *basicWriter) Write(buf []byte) (int, error) { + b.maybeWriteHeader() + n, err := b.ResponseWriter.Write(buf) + if b.tee != nil { + _, err2 := b.tee.Write(buf[:n]) + // Prefer errors generated by the proxied writer. + if err == nil { + err = err2 + } + } + b.bytes += n + return n, err +} + +func (b *basicWriter) maybeWriteHeader() { + if !b.wroteHeader { + b.WriteHeader(http.StatusOK) + } +} + +func (b *basicWriter) Status() int { + return b.code +} + +func (b *basicWriter) BytesWritten() int { + return b.bytes +} + +func (b *basicWriter) Tee(w io.Writer) { + b.tee = w +} + +func (b *basicWriter) Unwrap() http.ResponseWriter { + return b.ResponseWriter +} + +type flushWriter struct { + basicWriter +} + +func (f *flushWriter) Flush() { + f.wroteHeader = true + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} + +var _ http.Flusher = &flushWriter{} + +// httpFancyWriter is a HTTP writer that additionally satisfies +// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case +// of wrapping the http.ResponseWriter that package http gives you, in order to +// make the proxied object support the full method set of the proxied object. +type httpFancyWriter struct { + basicWriter +} + +func (f *httpFancyWriter) Flush() { + f.wroteHeader = true + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} + +func (f *httpFancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { + hj := f.basicWriter.ResponseWriter.(http.Hijacker) + return hj.Hijack() +} + +func (f *http2FancyWriter) Push(target string, opts *http.PushOptions) error { + return f.basicWriter.ResponseWriter.(http.Pusher).Push(target, opts) +} + +func (f *httpFancyWriter) ReadFrom(r io.Reader) (int64, error) { + if f.basicWriter.tee != nil { + n, err := io.Copy(&f.basicWriter, r) + f.basicWriter.bytes += int(n) + return n, err + } + rf := f.basicWriter.ResponseWriter.(io.ReaderFrom) + f.basicWriter.maybeWriteHeader() + n, err := rf.ReadFrom(r) + f.basicWriter.bytes += int(n) + return n, err +} + +var _ http.Flusher = &httpFancyWriter{} +var _ http.Hijacker = &httpFancyWriter{} +var _ http.Pusher = &http2FancyWriter{} +var _ io.ReaderFrom = &httpFancyWriter{} + +// http2FancyWriter is a HTTP2 writer that additionally satisfies +// http.Flusher, and io.ReaderFrom. It exists for the common case +// of wrapping the http.ResponseWriter that package http gives you, in order to +// make the proxied object support the full method set of the proxied object. +type http2FancyWriter struct { + basicWriter +} + +func (f *http2FancyWriter) Flush() { + f.wroteHeader = true + fl := f.basicWriter.ResponseWriter.(http.Flusher) + fl.Flush() +} + +var _ http.Flusher = &http2FancyWriter{} diff --git a/vendor/github.com/go-chi/chi/mux.go b/vendor/github.com/go-chi/chi/mux.go new file mode 100644 index 0000000000..52950e97b5 --- /dev/null +++ b/vendor/github.com/go-chi/chi/mux.go @@ -0,0 +1,466 @@ +package chi + +import ( + "context" + "fmt" + "net/http" + "strings" + "sync" +) + +var _ Router = &Mux{} + +// Mux is a simple HTTP route multiplexer that parses a request path, +// records any URL params, and executes an end handler. It implements +// the http.Handler interface and is friendly with the standard library. +// +// Mux is designed to be fast, minimal and offer a powerful API for building +// modular and composable HTTP services with a large set of handlers. It's +// particularly useful for writing large REST API services that break a handler +// into many smaller parts composed of middlewares and end handlers. +type Mux struct { + // The radix trie router + tree *node + + // The middleware stack + middlewares []func(http.Handler) http.Handler + + // Controls the behaviour of middleware chain generation when a mux + // is registered as an inline group inside another mux. + inline bool + parent *Mux + + // The computed mux handler made of the chained middleware stack and + // the tree router + handler http.Handler + + // Routing context pool + pool *sync.Pool + + // Custom route not found handler + notFoundHandler http.HandlerFunc + + // Custom method not allowed handler + methodNotAllowedHandler http.HandlerFunc +} + +// NewMux returns a newly initialized Mux object that implements the Router +// interface. +func NewMux() *Mux { + mux := &Mux{tree: &node{}, pool: &sync.Pool{}} + mux.pool.New = func() interface{} { + return NewRouteContext() + } + return mux +} + +// ServeHTTP is the single method of the http.Handler interface that makes +// Mux interoperable with the standard library. It uses a sync.Pool to get and +// reuse routing contexts for each request. +func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // Ensure the mux has some routes defined on the mux + if mx.handler == nil { + mx.NotFoundHandler().ServeHTTP(w, r) + return + } + + // Check if a routing context already exists from a parent router. + rctx, _ := r.Context().Value(RouteCtxKey).(*Context) + if rctx != nil { + mx.handler.ServeHTTP(w, r) + return + } + + // Fetch a RouteContext object from the sync pool, and call the computed + // mx.handler that is comprised of mx.middlewares + mx.routeHTTP. + // Once the request is finished, reset the routing context and put it back + // into the pool for reuse from another request. + rctx = mx.pool.Get().(*Context) + rctx.Reset() + rctx.Routes = mx + + // NOTE: r.WithContext() causes 2 allocations and context.WithValue() causes 1 allocation + r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx)) + + // Serve the request and once its done, put the request context back in the sync pool + mx.handler.ServeHTTP(w, r) + mx.pool.Put(rctx) +} + +// Use appends a middleware handler to the Mux middleware stack. +// +// The middleware stack for any Mux will execute before searching for a matching +// route to a specific handler, which provides opportunity to respond early, +// change the course of the request execution, or set request-scoped values for +// the next http.Handler. +func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) { + if mx.handler != nil { + panic("chi: all middlewares must be defined before routes on a mux") + } + mx.middlewares = append(mx.middlewares, middlewares...) +} + +// Handle adds the route `pattern` that matches any http method to +// execute the `handler` http.Handler. +func (mx *Mux) Handle(pattern string, handler http.Handler) { + mx.handle(mALL, pattern, handler) +} + +// HandleFunc adds the route `pattern` that matches any http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mALL, pattern, handlerFn) +} + +// Method adds the route `pattern` that matches `method` http method to +// execute the `handler` http.Handler. +func (mx *Mux) Method(method, pattern string, handler http.Handler) { + m, ok := methodMap[strings.ToUpper(method)] + if !ok { + panic(fmt.Sprintf("chi: '%s' http method is not supported.", method)) + } + mx.handle(m, pattern, handler) +} + +// MethodFunc adds the route `pattern` that matches `method` http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) { + mx.Method(method, pattern, handlerFn) +} + +// Connect adds the route `pattern` that matches a CONNECT http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mCONNECT, pattern, handlerFn) +} + +// Delete adds the route `pattern` that matches a DELETE http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mDELETE, pattern, handlerFn) +} + +// Get adds the route `pattern` that matches a GET http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mGET, pattern, handlerFn) +} + +// Head adds the route `pattern` that matches a HEAD http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mHEAD, pattern, handlerFn) +} + +// Options adds the route `pattern` that matches a OPTIONS http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mOPTIONS, pattern, handlerFn) +} + +// Patch adds the route `pattern` that matches a PATCH http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mPATCH, pattern, handlerFn) +} + +// Post adds the route `pattern` that matches a POST http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mPOST, pattern, handlerFn) +} + +// Put adds the route `pattern` that matches a PUT http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mPUT, pattern, handlerFn) +} + +// Trace adds the route `pattern` that matches a TRACE http method to +// execute the `handlerFn` http.HandlerFunc. +func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) { + mx.handle(mTRACE, pattern, handlerFn) +} + +// NotFound sets a custom http.HandlerFunc for routing paths that could +// not be found. The default 404 handler is `http.NotFound`. +func (mx *Mux) NotFound(handlerFn http.HandlerFunc) { + // Build NotFound handler chain + m := mx + hFn := handlerFn + if mx.inline && mx.parent != nil { + m = mx.parent + hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP + } + + // Update the notFoundHandler from this point forward + m.notFoundHandler = hFn + m.updateSubRoutes(func(subMux *Mux) { + if subMux.notFoundHandler == nil { + subMux.NotFound(hFn) + } + }) +} + +// MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the +// method is unresolved. The default handler returns a 405 with an empty body. +func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) { + // Build MethodNotAllowed handler chain + m := mx + hFn := handlerFn + if mx.inline && mx.parent != nil { + m = mx.parent + hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP + } + + // Update the methodNotAllowedHandler from this point forward + m.methodNotAllowedHandler = hFn + m.updateSubRoutes(func(subMux *Mux) { + if subMux.methodNotAllowedHandler == nil { + subMux.MethodNotAllowed(hFn) + } + }) +} + +// With adds inline middlewares for an endpoint handler. +func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router { + // Similarly as in handle(), we must build the mux handler once additional + // middleware registration isn't allowed for this stack, like now. + if !mx.inline && mx.handler == nil { + mx.buildRouteHandler() + } + + // Copy middlewares from parent inline muxs + var mws Middlewares + if mx.inline { + mws = make(Middlewares, len(mx.middlewares)) + copy(mws, mx.middlewares) + } + mws = append(mws, middlewares...) + + im := &Mux{ + pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws, + notFoundHandler: mx.notFoundHandler, methodNotAllowedHandler: mx.methodNotAllowedHandler, + } + + return im +} + +// Group creates a new inline-Mux with a fresh middleware stack. It's useful +// for a group of handlers along the same routing path that use an additional +// set of middlewares. See _examples/. +func (mx *Mux) Group(fn func(r Router)) Router { + im := mx.With().(*Mux) + if fn != nil { + fn(im) + } + return im +} + +// Route creates a new Mux with a fresh middleware stack and mounts it +// along the `pattern` as a subrouter. Effectively, this is a short-hand +// call to Mount. See _examples/. +func (mx *Mux) Route(pattern string, fn func(r Router)) Router { + subRouter := NewRouter() + if fn != nil { + fn(subRouter) + } + mx.Mount(pattern, subRouter) + return subRouter +} + +// Mount attaches another http.Handler or chi Router as a subrouter along a routing +// path. It's very useful to split up a large API as many independent routers and +// compose them as a single service using Mount. See _examples/. +// +// Note that Mount() simply sets a wildcard along the `pattern` that will continue +// routing at the `handler`, which in most cases is another chi.Router. As a result, +// if you define two Mount() routes on the exact same pattern the mount will panic. +func (mx *Mux) Mount(pattern string, handler http.Handler) { + // Provide runtime safety for ensuring a pattern isn't mounted on an existing + // routing pattern. + if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") { + panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern)) + } + + // Assign sub-Router's with the parent not found & method not allowed handler if not specified. + subr, ok := handler.(*Mux) + if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil { + subr.NotFound(mx.notFoundHandler) + } + if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil { + subr.MethodNotAllowed(mx.methodNotAllowedHandler) + } + + mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rctx := RouteContext(r.Context()) + rctx.RoutePath = mx.nextRoutePath(rctx) + handler.ServeHTTP(w, r) + }) + + if pattern == "" || pattern[len(pattern)-1] != '/' { + mx.handle(mALL|mSTUB, pattern, mountHandler) + mx.handle(mALL|mSTUB, pattern+"/", mountHandler) + pattern += "/" + } + + method := mALL + subroutes, _ := handler.(Routes) + if subroutes != nil { + method |= mSTUB + } + n := mx.handle(method, pattern+"*", mountHandler) + + if subroutes != nil { + n.subroutes = subroutes + } +} + +// Routes returns a slice of routing information from the tree, +// useful for traversing available routes of a router. +func (mx *Mux) Routes() []Route { + return mx.tree.routes() +} + +// Middlewares returns a slice of middleware handler functions. +func (mx *Mux) Middlewares() Middlewares { + return mx.middlewares +} + +// Match searches the routing tree for a handler that matches the method/path. +// It's similar to routing a http request, but without executing the handler +// thereafter. +// +// Note: the *Context state is updated during execution, so manage +// the state carefully or make a NewRouteContext(). +func (mx *Mux) Match(rctx *Context, method, path string) bool { + m, ok := methodMap[method] + if !ok { + return false + } + + node, _, h := mx.tree.FindRoute(rctx, m, path) + + if node != nil && node.subroutes != nil { + rctx.RoutePath = mx.nextRoutePath(rctx) + return node.subroutes.Match(rctx, method, rctx.RoutePath) + } + + return h != nil +} + +// NotFoundHandler returns the default Mux 404 responder whenever a route +// cannot be found. +func (mx *Mux) NotFoundHandler() http.HandlerFunc { + if mx.notFoundHandler != nil { + return mx.notFoundHandler + } + return http.NotFound +} + +// MethodNotAllowedHandler returns the default Mux 405 responder whenever +// a method cannot be resolved for a route. +func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc { + if mx.methodNotAllowedHandler != nil { + return mx.methodNotAllowedHandler + } + return methodNotAllowedHandler +} + +// buildRouteHandler builds the single mux handler that is a chain of the middleware +// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this +// point, no other middlewares can be registered on this Mux's stack. But you can still +// compose additional middlewares via Group()'s or using a chained middleware handler. +func (mx *Mux) buildRouteHandler() { + mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP)) +} + +// handle registers a http.Handler in the routing tree for a particular http method +// and routing pattern. +func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node { + if len(pattern) == 0 || pattern[0] != '/' { + panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern)) + } + + // Build the computed routing handler for this routing pattern. + if !mx.inline && mx.handler == nil { + mx.buildRouteHandler() + } + + // Build endpoint handler with inline middlewares for the route + var h http.Handler + if mx.inline { + mx.handler = http.HandlerFunc(mx.routeHTTP) + h = Chain(mx.middlewares...).Handler(handler) + } else { + h = handler + } + + // Add the endpoint to the tree and return the node + return mx.tree.InsertRoute(method, pattern, h) +} + +// routeHTTP routes a http.Request through the Mux routing tree to serve +// the matching handler for a particular http method. +func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) { + // Grab the route context object + rctx := r.Context().Value(RouteCtxKey).(*Context) + + // The request routing path + routePath := rctx.RoutePath + if routePath == "" { + if r.URL.RawPath != "" { + routePath = r.URL.RawPath + } else { + routePath = r.URL.Path + } + } + + // Check if method is supported by chi + if rctx.RouteMethod == "" { + rctx.RouteMethod = r.Method + } + method, ok := methodMap[rctx.RouteMethod] + if !ok { + mx.MethodNotAllowedHandler().ServeHTTP(w, r) + return + } + + // Find the route + if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil { + h.ServeHTTP(w, r) + return + } + if rctx.methodNotAllowed { + mx.MethodNotAllowedHandler().ServeHTTP(w, r) + } else { + mx.NotFoundHandler().ServeHTTP(w, r) + } +} + +func (mx *Mux) nextRoutePath(rctx *Context) string { + routePath := "/" + nx := len(rctx.routeParams.Keys) - 1 // index of last param in list + if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx { + routePath = "/" + rctx.routeParams.Values[nx] + } + return routePath +} + +// Recursively update data on child routers. +func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) { + for _, r := range mx.tree.routes() { + subMux, ok := r.SubRoutes.(*Mux) + if !ok { + continue + } + fn(subMux) + } +} + +// methodNotAllowedHandler is a helper function to respond with a 405, +// method not allowed. +func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(405) + w.Write(nil) +} diff --git a/vendor/github.com/go-chi/chi/tree.go b/vendor/github.com/go-chi/chi/tree.go new file mode 100644 index 0000000000..59b5b5f7b0 --- /dev/null +++ b/vendor/github.com/go-chi/chi/tree.go @@ -0,0 +1,865 @@ +package chi + +// Radix tree implementation below is a based on the original work by +// Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go +// (MIT licensed). It's been heavily modified for use as a HTTP routing tree. + +import ( + "fmt" + "math" + "net/http" + "regexp" + "sort" + "strconv" + "strings" +) + +type methodTyp int + +const ( + mSTUB methodTyp = 1 << iota + mCONNECT + mDELETE + mGET + mHEAD + mOPTIONS + mPATCH + mPOST + mPUT + mTRACE +) + +var mALL = mCONNECT | mDELETE | mGET | mHEAD | + mOPTIONS | mPATCH | mPOST | mPUT | mTRACE + +var methodMap = map[string]methodTyp{ + http.MethodConnect: mCONNECT, + http.MethodDelete: mDELETE, + http.MethodGet: mGET, + http.MethodHead: mHEAD, + http.MethodOptions: mOPTIONS, + http.MethodPatch: mPATCH, + http.MethodPost: mPOST, + http.MethodPut: mPUT, + http.MethodTrace: mTRACE, +} + +// RegisterMethod adds support for custom HTTP method handlers, available +// via Router#Method and Router#MethodFunc +func RegisterMethod(method string) { + if method == "" { + return + } + method = strings.ToUpper(method) + if _, ok := methodMap[method]; ok { + return + } + n := len(methodMap) + if n > strconv.IntSize { + panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize)) + } + mt := methodTyp(math.Exp2(float64(n))) + methodMap[method] = mt + mALL |= mt +} + +type nodeTyp uint8 + +const ( + ntStatic nodeTyp = iota // /home + ntRegexp // /{id:[0-9]+} + ntParam // /{user} + ntCatchAll // /api/v1/* +) + +type node struct { + // node type: static, regexp, param, catchAll + typ nodeTyp + + // first byte of the prefix + label byte + + // first byte of the child prefix + tail byte + + // prefix is the common prefix we ignore + prefix string + + // regexp matcher for regexp nodes + rex *regexp.Regexp + + // HTTP handler endpoints on the leaf node + endpoints endpoints + + // subroutes on the leaf node + subroutes Routes + + // child nodes should be stored in-order for iteration, + // in groups of the node type. + children [ntCatchAll + 1]nodes +} + +// endpoints is a mapping of http method constants to handlers +// for a given route. +type endpoints map[methodTyp]*endpoint + +type endpoint struct { + // endpoint handler + handler http.Handler + + // pattern is the routing pattern for handler nodes + pattern string + + // parameter keys recorded on handler nodes + paramKeys []string +} + +func (s endpoints) Value(method methodTyp) *endpoint { + mh, ok := s[method] + if !ok { + mh = &endpoint{} + s[method] = mh + } + return mh +} + +func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node { + var parent *node + search := pattern + + for { + // Handle key exhaustion + if len(search) == 0 { + // Insert or update the node's leaf handler + n.setEndpoint(method, handler, pattern) + return n + } + + // We're going to be searching for a wild node next, + // in this case, we need to get the tail + var label = search[0] + var segTail byte + var segEndIdx int + var segTyp nodeTyp + var segRexpat string + if label == '{' || label == '*' { + segTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search) + } + + var prefix string + if segTyp == ntRegexp { + prefix = segRexpat + } + + // Look for the edge to attach to + parent = n + n = n.getEdge(segTyp, label, segTail, prefix) + + // No edge, create one + if n == nil { + child := &node{label: label, tail: segTail, prefix: search} + hn := parent.addChild(child, search) + hn.setEndpoint(method, handler, pattern) + + return hn + } + + // Found an edge to match the pattern + + if n.typ > ntStatic { + // We found a param node, trim the param from the search path and continue. + // This param/wild pattern segment would already be on the tree from a previous + // call to addChild when creating a new node. + search = search[segEndIdx:] + continue + } + + // Static nodes fall below here. + // Determine longest prefix of the search key on match. + commonPrefix := longestPrefix(search, n.prefix) + if commonPrefix == len(n.prefix) { + // the common prefix is as long as the current node's prefix we're attempting to insert. + // keep the search going. + search = search[commonPrefix:] + continue + } + + // Split the node + child := &node{ + typ: ntStatic, + prefix: search[:commonPrefix], + } + parent.replaceChild(search[0], segTail, child) + + // Restore the existing node + n.label = n.prefix[commonPrefix] + n.prefix = n.prefix[commonPrefix:] + child.addChild(n, n.prefix) + + // If the new key is a subset, set the method/handler on this node and finish. + search = search[commonPrefix:] + if len(search) == 0 { + child.setEndpoint(method, handler, pattern) + return child + } + + // Create a new edge for the node + subchild := &node{ + typ: ntStatic, + label: search[0], + prefix: search, + } + hn := child.addChild(subchild, search) + hn.setEndpoint(method, handler, pattern) + return hn + } +} + +// addChild appends the new `child` node to the tree using the `pattern` as the trie key. +// For a URL router like chi's, we split the static, param, regexp and wildcard segments +// into different nodes. In addition, addChild will recursively call itself until every +// pattern segment is added to the url pattern tree as individual nodes, depending on type. +func (n *node) addChild(child *node, prefix string) *node { + search := prefix + + // handler leaf node added to the tree is the child. + // this may be overridden later down the flow + hn := child + + // Parse next segment + segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search) + + // Add child depending on next up segment + switch segTyp { + + case ntStatic: + // Search prefix is all static (that is, has no params in path) + // noop + + default: + // Search prefix contains a param, regexp or wildcard + + if segTyp == ntRegexp { + rex, err := regexp.Compile(segRexpat) + if err != nil { + panic(fmt.Sprintf("chi: invalid regexp pattern '%s' in route param", segRexpat)) + } + child.prefix = segRexpat + child.rex = rex + } + + if segStartIdx == 0 { + // Route starts with a param + child.typ = segTyp + + if segTyp == ntCatchAll { + segStartIdx = -1 + } else { + segStartIdx = segEndIdx + } + if segStartIdx < 0 { + segStartIdx = len(search) + } + child.tail = segTail // for params, we set the tail + + if segStartIdx != len(search) { + // add static edge for the remaining part, split the end. + // its not possible to have adjacent param nodes, so its certainly + // going to be a static node next. + + search = search[segStartIdx:] // advance search position + + nn := &node{ + typ: ntStatic, + label: search[0], + prefix: search, + } + hn = child.addChild(nn, search) + } + + } else if segStartIdx > 0 { + // Route has some param + + // starts with a static segment + child.typ = ntStatic + child.prefix = search[:segStartIdx] + child.rex = nil + + // add the param edge node + search = search[segStartIdx:] + + nn := &node{ + typ: segTyp, + label: search[0], + tail: segTail, + } + hn = child.addChild(nn, search) + + } + } + + n.children[child.typ] = append(n.children[child.typ], child) + n.children[child.typ].Sort() + return hn +} + +func (n *node) replaceChild(label, tail byte, child *node) { + for i := 0; i < len(n.children[child.typ]); i++ { + if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail { + n.children[child.typ][i] = child + n.children[child.typ][i].label = label + n.children[child.typ][i].tail = tail + return + } + } + panic("chi: replacing missing child") +} + +func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node { + nds := n.children[ntyp] + for i := 0; i < len(nds); i++ { + if nds[i].label == label && nds[i].tail == tail { + if ntyp == ntRegexp && nds[i].prefix != prefix { + continue + } + return nds[i] + } + } + return nil +} + +func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) { + // Set the handler for the method type on the node + if n.endpoints == nil { + n.endpoints = make(endpoints) + } + + paramKeys := patParamKeys(pattern) + + if method&mSTUB == mSTUB { + n.endpoints.Value(mSTUB).handler = handler + } + if method&mALL == mALL { + h := n.endpoints.Value(mALL) + h.handler = handler + h.pattern = pattern + h.paramKeys = paramKeys + for _, m := range methodMap { + h := n.endpoints.Value(m) + h.handler = handler + h.pattern = pattern + h.paramKeys = paramKeys + } + } else { + h := n.endpoints.Value(method) + h.handler = handler + h.pattern = pattern + h.paramKeys = paramKeys + } +} + +func (n *node) FindRoute(rctx *Context, method methodTyp, path string) (*node, endpoints, http.Handler) { + // Reset the context routing pattern and params + rctx.routePattern = "" + rctx.routeParams.Keys = rctx.routeParams.Keys[:0] + rctx.routeParams.Values = rctx.routeParams.Values[:0] + + // Find the routing handlers for the path + rn := n.findRoute(rctx, method, path) + if rn == nil { + return nil, nil, nil + } + + // Record the routing params in the request lifecycle + rctx.URLParams.Keys = append(rctx.URLParams.Keys, rctx.routeParams.Keys...) + rctx.URLParams.Values = append(rctx.URLParams.Values, rctx.routeParams.Values...) + + // Record the routing pattern in the request lifecycle + if rn.endpoints[method].pattern != "" { + rctx.routePattern = rn.endpoints[method].pattern + rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern) + } + + return rn, rn.endpoints, rn.endpoints[method].handler +} + +// Recursive edge traversal by checking all nodeTyp groups along the way. +// It's like searching through a multi-dimensional radix trie. +func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node { + nn := n + search := path + + for t, nds := range nn.children { + ntyp := nodeTyp(t) + if len(nds) == 0 { + continue + } + + var xn *node + xsearch := search + + var label byte + if search != "" { + label = search[0] + } + + switch ntyp { + case ntStatic: + xn = nds.findEdge(label) + if xn == nil || !strings.HasPrefix(xsearch, xn.prefix) { + continue + } + xsearch = xsearch[len(xn.prefix):] + + case ntParam, ntRegexp: + // short-circuit and return no matching route for empty param values + if xsearch == "" { + continue + } + + // serially loop through each node grouped by the tail delimiter + for idx := 0; idx < len(nds); idx++ { + xn = nds[idx] + + // label for param nodes is the delimiter byte + p := strings.IndexByte(xsearch, xn.tail) + + if p < 0 { + if xn.tail == '/' { + p = len(xsearch) + } else { + continue + } + } + + if ntyp == ntRegexp && xn.rex != nil { + if !xn.rex.Match([]byte(xsearch[:p])) { + continue + } + } else if strings.IndexByte(xsearch[:p], '/') != -1 { + // avoid a match across path segments + continue + } + + prevlen := len(rctx.routeParams.Values) + rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p]) + xsearch = xsearch[p:] + + if len(xsearch) == 0 { + if xn.isLeaf() { + h := xn.endpoints[method] + if h != nil && h.handler != nil { + rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) + return xn + } + + // flag that the routing context found a route, but not a corresponding + // supported method + rctx.methodNotAllowed = true + } + } + + // recursively find the next node on this branch + fin := xn.findRoute(rctx, method, xsearch) + if fin != nil { + return fin + } + + // not found on this branch, reset vars + rctx.routeParams.Values = rctx.routeParams.Values[:prevlen] + xsearch = search + } + + rctx.routeParams.Values = append(rctx.routeParams.Values, "") + + default: + // catch-all nodes + rctx.routeParams.Values = append(rctx.routeParams.Values, search) + xn = nds[0] + xsearch = "" + } + + if xn == nil { + continue + } + + // did we find it yet? + if len(xsearch) == 0 { + if xn.isLeaf() { + h := xn.endpoints[method] + if h != nil && h.handler != nil { + rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...) + return xn + } + + // flag that the routing context found a route, but not a corresponding + // supported method + rctx.methodNotAllowed = true + } + } + + // recursively find the next node.. + fin := xn.findRoute(rctx, method, xsearch) + if fin != nil { + return fin + } + + // Did not find final handler, let's remove the param here if it was set + if xn.typ > ntStatic { + if len(rctx.routeParams.Values) > 0 { + rctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values)-1] + } + } + + } + + return nil +} + +func (n *node) findEdge(ntyp nodeTyp, label byte) *node { + nds := n.children[ntyp] + num := len(nds) + idx := 0 + + switch ntyp { + case ntStatic, ntParam, ntRegexp: + i, j := 0, num-1 + for i <= j { + idx = i + (j-i)/2 + if label > nds[idx].label { + i = idx + 1 + } else if label < nds[idx].label { + j = idx - 1 + } else { + i = num // breaks cond + } + } + if nds[idx].label != label { + return nil + } + return nds[idx] + + default: // catch all + return nds[idx] + } +} + +func (n *node) isLeaf() bool { + return n.endpoints != nil +} + +func (n *node) findPattern(pattern string) bool { + nn := n + for _, nds := range nn.children { + if len(nds) == 0 { + continue + } + + n = nn.findEdge(nds[0].typ, pattern[0]) + if n == nil { + continue + } + + var idx int + var xpattern string + + switch n.typ { + case ntStatic: + idx = longestPrefix(pattern, n.prefix) + if idx < len(n.prefix) { + continue + } + + case ntParam, ntRegexp: + idx = strings.IndexByte(pattern, '}') + 1 + + case ntCatchAll: + idx = longestPrefix(pattern, "*") + + default: + panic("chi: unknown node type") + } + + xpattern = pattern[idx:] + if len(xpattern) == 0 { + return true + } + + return n.findPattern(xpattern) + } + return false +} + +func (n *node) routes() []Route { + rts := []Route{} + + n.walk(func(eps endpoints, subroutes Routes) bool { + if eps[mSTUB] != nil && eps[mSTUB].handler != nil && subroutes == nil { + return false + } + + // Group methodHandlers by unique patterns + pats := make(map[string]endpoints) + + for mt, h := range eps { + if h.pattern == "" { + continue + } + p, ok := pats[h.pattern] + if !ok { + p = endpoints{} + pats[h.pattern] = p + } + p[mt] = h + } + + for p, mh := range pats { + hs := make(map[string]http.Handler) + if mh[mALL] != nil && mh[mALL].handler != nil { + hs["*"] = mh[mALL].handler + } + + for mt, h := range mh { + if h.handler == nil { + continue + } + m := methodTypString(mt) + if m == "" { + continue + } + hs[m] = h.handler + } + + rt := Route{p, hs, subroutes} + rts = append(rts, rt) + } + + return false + }) + + return rts +} + +func (n *node) walk(fn func(eps endpoints, subroutes Routes) bool) bool { + // Visit the leaf values if any + if (n.endpoints != nil || n.subroutes != nil) && fn(n.endpoints, n.subroutes) { + return true + } + + // Recurse on the children + for _, ns := range n.children { + for _, cn := range ns { + if cn.walk(fn) { + return true + } + } + } + return false +} + +// patNextSegment returns the next segment details from a pattern: +// node type, param key, regexp string, param tail byte, param starting index, param ending index +func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) { + ps := strings.Index(pattern, "{") + ws := strings.Index(pattern, "*") + + if ps < 0 && ws < 0 { + return ntStatic, "", "", 0, 0, len(pattern) // we return the entire thing + } + + // Sanity check + if ps >= 0 && ws >= 0 && ws < ps { + panic("chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'") + } + + var tail byte = '/' // Default endpoint tail to / byte + + if ps >= 0 { + // Param/Regexp pattern is next + nt := ntParam + + // Read to closing } taking into account opens and closes in curl count (cc) + cc := 0 + pe := ps + for i, c := range pattern[ps:] { + if c == '{' { + cc++ + } else if c == '}' { + cc-- + if cc == 0 { + pe = ps + i + break + } + } + } + if pe == ps { + panic("chi: route param closing delimiter '}' is missing") + } + + key := pattern[ps+1 : pe] + pe++ // set end to next position + + if pe < len(pattern) { + tail = pattern[pe] + } + + var rexpat string + if idx := strings.Index(key, ":"); idx >= 0 { + nt = ntRegexp + rexpat = key[idx+1:] + key = key[:idx] + } + + if len(rexpat) > 0 { + if rexpat[0] != '^' { + rexpat = "^" + rexpat + } + if rexpat[len(rexpat)-1] != '$' { + rexpat += "$" + } + } + + return nt, key, rexpat, tail, ps, pe + } + + // Wildcard pattern as finale + if ws < len(pattern)-1 { + panic("chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead") + } + return ntCatchAll, "*", "", 0, ws, len(pattern) +} + +func patParamKeys(pattern string) []string { + pat := pattern + paramKeys := []string{} + for { + ptyp, paramKey, _, _, _, e := patNextSegment(pat) + if ptyp == ntStatic { + return paramKeys + } + for i := 0; i < len(paramKeys); i++ { + if paramKeys[i] == paramKey { + panic(fmt.Sprintf("chi: routing pattern '%s' contains duplicate param key, '%s'", pattern, paramKey)) + } + } + paramKeys = append(paramKeys, paramKey) + pat = pat[e:] + } +} + +// longestPrefix finds the length of the shared prefix +// of two strings +func longestPrefix(k1, k2 string) int { + max := len(k1) + if l := len(k2); l < max { + max = l + } + var i int + for i = 0; i < max; i++ { + if k1[i] != k2[i] { + break + } + } + return i +} + +func methodTypString(method methodTyp) string { + for s, t := range methodMap { + if method == t { + return s + } + } + return "" +} + +type nodes []*node + +// Sort the list of nodes by label +func (ns nodes) Sort() { sort.Sort(ns); ns.tailSort() } +func (ns nodes) Len() int { return len(ns) } +func (ns nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] } +func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label } + +// tailSort pushes nodes with '/' as the tail to the end of the list for param nodes. +// The list order determines the traversal order. +func (ns nodes) tailSort() { + for i := len(ns) - 1; i >= 0; i-- { + if ns[i].typ > ntStatic && ns[i].tail == '/' { + ns.Swap(i, len(ns)-1) + return + } + } +} + +func (ns nodes) findEdge(label byte) *node { + num := len(ns) + idx := 0 + i, j := 0, num-1 + for i <= j { + idx = i + (j-i)/2 + if label > ns[idx].label { + i = idx + 1 + } else if label < ns[idx].label { + j = idx - 1 + } else { + i = num // breaks cond + } + } + if ns[idx].label != label { + return nil + } + return ns[idx] +} + +// Route describes the details of a routing handler. +// Handlers map key is an HTTP method +type Route struct { + Pattern string + Handlers map[string]http.Handler + SubRoutes Routes +} + +// WalkFunc is the type of the function called for each method and route visited by Walk. +type WalkFunc func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error + +// Walk walks any router tree that implements Routes interface. +func Walk(r Routes, walkFn WalkFunc) error { + return walk(r, walkFn, "") +} + +func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.Handler) http.Handler) error { + for _, route := range r.Routes() { + mws := make([]func(http.Handler) http.Handler, len(parentMw)) + copy(mws, parentMw) + mws = append(mws, r.Middlewares()...) + + if route.SubRoutes != nil { + if err := walk(route.SubRoutes, walkFn, parentRoute+route.Pattern, mws...); err != nil { + return err + } + continue + } + + for method, handler := range route.Handlers { + if method == "*" { + // Ignore a "catchAll" method, since we pass down all the specific methods for each route. + continue + } + + fullRoute := parentRoute + route.Pattern + fullRoute = strings.Replace(fullRoute, "/*/", "/", -1) + + if chain, ok := handler.(*ChainHandler); ok { + if err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil { + return err + } + } else { + if err := walkFn(method, fullRoute, handler, mws...); err != nil { + return err + } + } + } + } + + return nil +} diff --git a/vendor/github.com/go-chi/cors/LICENSE b/vendor/github.com/go-chi/cors/LICENSE new file mode 100644 index 0000000000..aee6182f9a --- /dev/null +++ b/vendor/github.com/go-chi/cors/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2014 Olivier Poitrey +Copyright (c) 2016-Present https://github.com/go-chi authors + +MIT License + +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. diff --git a/vendor/github.com/go-chi/cors/README.md b/vendor/github.com/go-chi/cors/README.md new file mode 100644 index 0000000000..b41686b6af --- /dev/null +++ b/vendor/github.com/go-chi/cors/README.md @@ -0,0 +1,39 @@ +# CORS net/http middleware + +[go-chi/cors](https://github.com/go-chi/cors) is a fork of [github.com/rs/cors](https://github.com/rs/cors) that +provides a `net/http` compatible middleware for performing preflight CORS checks on the server side. These headers +are required for using the browser native [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). + +This middleware is designed to be used as a top-level middleware on the [chi](https://github.com/go-chi/chi) router. +Applying with within a `r.Group()` or using `With()` will not work without routes matching `OPTIONS` added. + +## Usage + +```go +func main() { + r := chi.NewRouter() + + // Basic CORS + // for more ideas, see: https://developer.github.com/v3/#cross-origin-resource-sharing + r.Use(cors.Handler(cors.Options{ + // AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts + AllowedOrigins: []string{"https://*", "http://*"}, + // AllowOriginFunc: func(r *http.Request, origin string) bool { return true }, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, + ExposedHeaders: []string{"Link"}, + AllowCredentials: false, + MaxAge: 300, // Maximum value not ignored by any of major browsers + })) + + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("welcome")) + }) + + http.ListenAndServe(":3000", r) +} +``` + +## Credits + +All credit for the original work of this middleware goes out to [github.com/rs](github.com/rs). diff --git a/vendor/github.com/go-chi/cors/cors.go b/vendor/github.com/go-chi/cors/cors.go new file mode 100644 index 0000000000..8df81636e3 --- /dev/null +++ b/vendor/github.com/go-chi/cors/cors.go @@ -0,0 +1,400 @@ +// cors package is net/http handler to handle CORS related requests +// as defined by http://www.w3.org/TR/cors/ +// +// You can configure it by passing an option struct to cors.New: +// +// c := cors.New(cors.Options{ +// AllowedOrigins: []string{"foo.com"}, +// AllowedMethods: []string{"GET", "POST", "DELETE"}, +// AllowCredentials: true, +// }) +// +// Then insert the handler in the chain: +// +// handler = c.Handler(handler) +// +// See Options documentation for more options. +// +// The resulting handler is a standard net/http handler. +package cors + +import ( + "log" + "net/http" + "os" + "strconv" + "strings" +) + +// Options is a configuration container to setup the CORS middleware. +type Options struct { + // AllowedOrigins is a list of origins a cross-domain request can be executed from. + // If the special "*" value is present in the list, all origins will be allowed. + // An origin may contain a wildcard (*) to replace 0 or more characters + // (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penalty. + // Only one wildcard can be used per origin. + // Default value is ["*"] + AllowedOrigins []string + + // AllowOriginFunc is a custom function to validate the origin. It takes the origin + // as argument and returns true if allowed or false otherwise. If this option is + // set, the content of AllowedOrigins is ignored. + AllowOriginFunc func(r *http.Request, origin string) bool + + // AllowedMethods is a list of methods the client is allowed to use with + // cross-domain requests. Default value is simple methods (HEAD, GET and POST). + AllowedMethods []string + + // AllowedHeaders is list of non simple headers the client is allowed to use with + // cross-domain requests. + // If the special "*" value is present in the list, all headers will be allowed. + // Default value is [] but "Origin" is always appended to the list. + AllowedHeaders []string + + // ExposedHeaders indicates which headers are safe to expose to the API of a CORS + // API specification + ExposedHeaders []string + + // AllowCredentials indicates whether the request can include user credentials like + // cookies, HTTP authentication or client side SSL certificates. + AllowCredentials bool + + // MaxAge indicates how long (in seconds) the results of a preflight request + // can be cached + MaxAge int + + // OptionsPassthrough instructs preflight to let other potential next handlers to + // process the OPTIONS method. Turn this on if your application handles OPTIONS. + OptionsPassthrough bool + + // Debugging flag adds additional output to debug server side CORS issues + Debug bool +} + +// Logger generic interface for logger +type Logger interface { + Printf(string, ...interface{}) +} + +// Cors http handler +type Cors struct { + // Debug logger + Log Logger + + // Normalized list of plain allowed origins + allowedOrigins []string + + // List of allowed origins containing wildcards + allowedWOrigins []wildcard + + // Optional origin validator function + allowOriginFunc func(r *http.Request, origin string) bool + + // Normalized list of allowed headers + allowedHeaders []string + + // Normalized list of allowed methods + allowedMethods []string + + // Normalized list of exposed headers + exposedHeaders []string + maxAge int + + // Set to true when allowed origins contains a "*" + allowedOriginsAll bool + + // Set to true when allowed headers contains a "*" + allowedHeadersAll bool + + allowCredentials bool + optionPassthrough bool +} + +// New creates a new Cors handler with the provided options. +func New(options Options) *Cors { + c := &Cors{ + exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), + allowOriginFunc: options.AllowOriginFunc, + allowCredentials: options.AllowCredentials, + maxAge: options.MaxAge, + optionPassthrough: options.OptionsPassthrough, + } + if options.Debug && c.Log == nil { + c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags) + } + + // Normalize options + // Note: for origins and methods matching, the spec requires a case-sensitive matching. + // As it may error prone, we chose to ignore the spec here. + + // Allowed Origins + if len(options.AllowedOrigins) == 0 { + if options.AllowOriginFunc == nil { + // Default is all origins + c.allowedOriginsAll = true + } + } else { + c.allowedOrigins = []string{} + c.allowedWOrigins = []wildcard{} + for _, origin := range options.AllowedOrigins { + // Normalize + origin = strings.ToLower(origin) + if origin == "*" { + // If "*" is present in the list, turn the whole list into a match all + c.allowedOriginsAll = true + c.allowedOrigins = nil + c.allowedWOrigins = nil + break + } else if i := strings.IndexByte(origin, '*'); i >= 0 { + // Split the origin in two: start and end string without the * + w := wildcard{origin[0:i], origin[i+1:]} + c.allowedWOrigins = append(c.allowedWOrigins, w) + } else { + c.allowedOrigins = append(c.allowedOrigins, origin) + } + } + } + + // Allowed Headers + if len(options.AllowedHeaders) == 0 { + // Use sensible defaults + c.allowedHeaders = []string{"Origin", "Accept", "Content-Type"} + } else { + // Origin is always appended as some browsers will always request for this header at preflight + c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey) + for _, h := range options.AllowedHeaders { + if h == "*" { + c.allowedHeadersAll = true + c.allowedHeaders = nil + break + } + } + } + + // Allowed Methods + if len(options.AllowedMethods) == 0 { + // Default is spec's "simple" methods + c.allowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead} + } else { + c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper) + } + + return c +} + +// Handler creates a new Cors handler with passed options. +func Handler(options Options) func(next http.Handler) http.Handler { + c := New(options) + return c.Handler +} + +// AllowAll create a new Cors handler with permissive configuration allowing all +// origins with all standard methods with any header and credentials. +func AllowAll() *Cors { + return New(Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{ + http.MethodHead, + http.MethodGet, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + }, + AllowedHeaders: []string{"*"}, + AllowCredentials: false, + }) +} + +// Handler apply the CORS specification on the request, and add relevant CORS headers +// as necessary. +func (c *Cors) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Method") != "" { + c.logf("Handler: Preflight request") + c.handlePreflight(w, r) + // Preflight requests are standalone and should stop the chain as some other + // middleware may not handle OPTIONS requests correctly. One typical example + // is authentication middleware ; OPTIONS requests won't carry authentication + // headers (see #1) + if c.optionPassthrough { + next.ServeHTTP(w, r) + } else { + w.WriteHeader(http.StatusOK) + } + } else { + c.logf("Handler: Actual request") + c.handleActualRequest(w, r) + next.ServeHTTP(w, r) + } + }) +} + +// handlePreflight handles pre-flight CORS requests +func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { + headers := w.Header() + origin := r.Header.Get("Origin") + + if r.Method != http.MethodOptions { + c.logf("Preflight aborted: %s!=OPTIONS", r.Method) + return + } + // Always set Vary headers + // see https://github.com/rs/cors/issues/10, + // https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001 + headers.Add("Vary", "Origin") + headers.Add("Vary", "Access-Control-Request-Method") + headers.Add("Vary", "Access-Control-Request-Headers") + + if origin == "" { + c.logf("Preflight aborted: empty origin") + return + } + if !c.isOriginAllowed(r, origin) { + c.logf("Preflight aborted: origin '%s' not allowed", origin) + return + } + + reqMethod := r.Header.Get("Access-Control-Request-Method") + if !c.isMethodAllowed(reqMethod) { + c.logf("Preflight aborted: method '%s' not allowed", reqMethod) + return + } + reqHeaders := parseHeaderList(r.Header.Get("Access-Control-Request-Headers")) + if !c.areHeadersAllowed(reqHeaders) { + c.logf("Preflight aborted: headers '%v' not allowed", reqHeaders) + return + } + if c.allowedOriginsAll { + headers.Set("Access-Control-Allow-Origin", "*") + } else { + headers.Set("Access-Control-Allow-Origin", origin) + } + // Spec says: Since the list of methods can be unbounded, simply returning the method indicated + // by Access-Control-Request-Method (if supported) can be enough + headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod)) + if len(reqHeaders) > 0 { + + // Spec says: Since the list of headers can be unbounded, simply returning supported headers + // from Access-Control-Request-Headers can be enough + headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) + } + if c.allowCredentials { + headers.Set("Access-Control-Allow-Credentials", "true") + } + if c.maxAge > 0 { + headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge)) + } + c.logf("Preflight response headers: %v", headers) +} + +// handleActualRequest handles simple cross-origin requests, actual request or redirects +func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { + headers := w.Header() + origin := r.Header.Get("Origin") + + // Always set Vary, see https://github.com/rs/cors/issues/10 + headers.Add("Vary", "Origin") + if origin == "" { + c.logf("Actual request no headers added: missing origin") + return + } + if !c.isOriginAllowed(r, origin) { + c.logf("Actual request no headers added: origin '%s' not allowed", origin) + return + } + + // Note that spec does define a way to specifically disallow a simple method like GET or + // POST. Access-Control-Allow-Methods is only used for pre-flight requests and the + // spec doesn't instruct to check the allowed methods for simple cross-origin requests. + // We think it's a nice feature to be able to have control on those methods though. + if !c.isMethodAllowed(r.Method) { + c.logf("Actual request no headers added: method '%s' not allowed", r.Method) + + return + } + if c.allowedOriginsAll { + headers.Set("Access-Control-Allow-Origin", "*") + } else { + headers.Set("Access-Control-Allow-Origin", origin) + } + if len(c.exposedHeaders) > 0 { + headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", ")) + } + if c.allowCredentials { + headers.Set("Access-Control-Allow-Credentials", "true") + } + c.logf("Actual response added headers: %v", headers) +} + +// convenience method. checks if a logger is set. +func (c *Cors) logf(format string, a ...interface{}) { + if c.Log != nil { + c.Log.Printf(format, a...) + } +} + +// isOriginAllowed checks if a given origin is allowed to perform cross-domain requests +// on the endpoint +func (c *Cors) isOriginAllowed(r *http.Request, origin string) bool { + if c.allowOriginFunc != nil { + return c.allowOriginFunc(r, origin) + } + if c.allowedOriginsAll { + return true + } + origin = strings.ToLower(origin) + for _, o := range c.allowedOrigins { + if o == origin { + return true + } + } + for _, w := range c.allowedWOrigins { + if w.match(origin) { + return true + } + } + return false +} + +// isMethodAllowed checks if a given method can be used as part of a cross-domain request +// on the endpoint +func (c *Cors) isMethodAllowed(method string) bool { + if len(c.allowedMethods) == 0 { + // If no method allowed, always return false, even for preflight request + return false + } + method = strings.ToUpper(method) + if method == http.MethodOptions { + // Always allow preflight requests + return true + } + for _, m := range c.allowedMethods { + if m == method { + return true + } + } + return false +} + +// areHeadersAllowed checks if a given list of headers are allowed to used within +// a cross-domain request. +func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool { + if c.allowedHeadersAll || len(requestedHeaders) == 0 { + return true + } + for _, header := range requestedHeaders { + header = http.CanonicalHeaderKey(header) + found := false + for _, h := range c.allowedHeaders { + if h == header { + found = true + break + } + } + if !found { + return false + } + } + return true +} diff --git a/vendor/github.com/go-chi/cors/utils.go b/vendor/github.com/go-chi/cors/utils.go new file mode 100644 index 0000000000..3fe5a5aeeb --- /dev/null +++ b/vendor/github.com/go-chi/cors/utils.go @@ -0,0 +1,70 @@ +package cors + +import "strings" + +const toLower = 'a' - 'A' + +type converter func(string) string + +type wildcard struct { + prefix string + suffix string +} + +func (w wildcard) match(s string) bool { + return len(s) >= len(w.prefix+w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) +} + +// convert converts a list of string using the passed converter function +func convert(s []string, c converter) []string { + out := []string{} + for _, i := range s { + out = append(out, c(i)) + } + return out +} + +// parseHeaderList tokenize + normalize a string containing a list of headers +func parseHeaderList(headerList string) []string { + l := len(headerList) + h := make([]byte, 0, l) + upper := true + // Estimate the number headers in order to allocate the right splice size + t := 0 + for i := 0; i < l; i++ { + if headerList[i] == ',' { + t++ + } + } + headers := make([]string, 0, t) + for i := 0; i < l; i++ { + b := headerList[i] + if b >= 'a' && b <= 'z' { + if upper { + h = append(h, b-toLower) + } else { + h = append(h, b) + } + } else if b >= 'A' && b <= 'Z' { + if !upper { + h = append(h, b+toLower) + } else { + h = append(h, b) + } + } else if b == '-' || b == '_' || b == '.' || (b >= '0' && b <= '9') { + h = append(h, b) + } + + if b == ' ' || b == ',' || i == l-1 { + if len(h) > 0 { + // Flush the found header + headers = append(headers, string(h)) + h = h[:0] + upper = true + } + } else { + upper = b == '-' + } + } + return headers +} diff --git a/vendor/github.com/go-redis/redis/v8/.gitignore b/vendor/github.com/go-redis/redis/v8/.gitignore new file mode 100644 index 0000000000..b975a7b4c3 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/.gitignore @@ -0,0 +1,3 @@ +*.rdb +testdata/*/ +.idea/ diff --git a/vendor/github.com/go-redis/redis/v8/.golangci.yml b/vendor/github.com/go-redis/redis/v8/.golangci.yml new file mode 100644 index 0000000000..de514554a9 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/.golangci.yml @@ -0,0 +1,4 @@ +run: + concurrency: 8 + deadline: 5m + tests: false diff --git a/vendor/github.com/go-redis/redis/v8/.prettierrc.yml b/vendor/github.com/go-redis/redis/v8/.prettierrc.yml new file mode 100644 index 0000000000..8b7f044ad1 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/.prettierrc.yml @@ -0,0 +1,4 @@ +semi: false +singleQuote: true +proseWrap: always +printWidth: 100 diff --git a/vendor/github.com/go-redis/redis/v8/CHANGELOG.md b/vendor/github.com/go-redis/redis/v8/CHANGELOG.md new file mode 100644 index 0000000000..195e519338 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/CHANGELOG.md @@ -0,0 +1,177 @@ +## [8.11.5](https://github.com/go-redis/redis/compare/v8.11.4...v8.11.5) (2022-03-17) + + +### Bug Fixes + +* add missing Expire methods to Cmdable ([17e3b43](https://github.com/go-redis/redis/commit/17e3b43879d516437ada71cf9c0deac6a382ed9a)) +* add whitespace for avoid unlikely colisions ([7f7c181](https://github.com/go-redis/redis/commit/7f7c1817617cfec909efb13d14ad22ef05a6ad4c)) +* example/otel compile error ([#2028](https://github.com/go-redis/redis/issues/2028)) ([187c07c](https://github.com/go-redis/redis/commit/187c07c41bf68dc3ab280bc3a925e960bbef6475)) +* **extra/redisotel:** set span.kind attribute to client ([065b200](https://github.com/go-redis/redis/commit/065b200070b41e6e949710b4f9e01b50ccc60ab2)) +* format ([96f53a0](https://github.com/go-redis/redis/commit/96f53a0159a28affa94beec1543a62234e7f8b32)) +* invalid type assert in stringArg ([de6c131](https://github.com/go-redis/redis/commit/de6c131865b8263400c8491777b295035f2408e4)) +* rename Golang to Go ([#2030](https://github.com/go-redis/redis/issues/2030)) ([b82a2d9](https://github.com/go-redis/redis/commit/b82a2d9d4d2de7b7cbe8fcd4895be62dbcacacbc)) +* set timeout for WAIT command. Fixes [#1963](https://github.com/go-redis/redis/issues/1963) ([333fee1](https://github.com/go-redis/redis/commit/333fee1a8fd98a2fbff1ab187c1b03246a7eb01f)) +* update some argument counts in pre-allocs ([f6974eb](https://github.com/go-redis/redis/commit/f6974ebb5c40a8adf90d2cacab6dc297f4eba4c2)) + + +### Features + +* Add redis v7's NX, XX, GT, LT expire variants ([e19bbb2](https://github.com/go-redis/redis/commit/e19bbb26e2e395c6e077b48d80d79e99f729a8b8)) +* add support for acl sentinel auth in universal client ([ab0ccc4](https://github.com/go-redis/redis/commit/ab0ccc47413f9b2a6eabc852fed5005a3ee1af6e)) +* add support for COPY command ([#2016](https://github.com/go-redis/redis/issues/2016)) ([730afbc](https://github.com/go-redis/redis/commit/730afbcffb93760e8a36cc06cfe55ab102b693a7)) +* add support for passing extra attributes added to spans ([39faaa1](https://github.com/go-redis/redis/commit/39faaa171523834ba527c9789710c4fde87f5a2e)) +* add support for time.Duration write and scan ([2f1b74e](https://github.com/go-redis/redis/commit/2f1b74e20cdd7719b2aecf0768d3e3ae7c3e781b)) +* **redisotel:** ability to override TracerProvider ([#1998](https://github.com/go-redis/redis/issues/1998)) ([bf8d4aa](https://github.com/go-redis/redis/commit/bf8d4aa60c00366cda2e98c3ddddc8cf68507417)) +* set net.peer.name and net.peer.port in otel example ([69bf454](https://github.com/go-redis/redis/commit/69bf454f706204211cd34835f76b2e8192d3766d)) + + + +## [8.11.4](https://github.com/go-redis/redis/compare/v8.11.3...v8.11.4) (2021-10-04) + + +### Features + +* add acl auth support for sentinels ([f66582f](https://github.com/go-redis/redis/commit/f66582f44f3dc3a4705a5260f982043fde4aa634)) +* add Cmd.{String,Int,Float,Bool}Slice helpers and an example ([5d3d293](https://github.com/go-redis/redis/commit/5d3d293cc9c60b90871e2420602001463708ce24)) +* add SetVal method for each command ([168981d](https://github.com/go-redis/redis/commit/168981da2d84ee9e07d15d3e74d738c162e264c4)) + + + +## v8.11 + +- Remove OpenTelemetry metrics. +- Supports more redis commands and options. + +## v8.10 + +- Removed extra OpenTelemetry spans from go-redis core. Now go-redis instrumentation only adds a + single span with a Redis command (instead of 4 spans). There are multiple reasons behind this + decision: + + - Traces become smaller and less noisy. + - It may be costly to process those 3 extra spans for each query. + - go-redis no longer depends on OpenTelemetry. + + Eventually we hope to replace the information that we no longer collect with OpenTelemetry + Metrics. + +## v8.9 + +- Changed `PubSub.Channel` to only rely on `Ping` result. You can now use `WithChannelSize`, + `WithChannelHealthCheckInterval`, and `WithChannelSendTimeout` to override default settings. + +## v8.8 + +- To make updating easier, extra modules now have the same version as go-redis does. That means that + you need to update your imports: + +``` +github.com/go-redis/redis/extra/redisotel -> github.com/go-redis/redis/extra/redisotel/v8 +github.com/go-redis/redis/extra/rediscensus -> github.com/go-redis/redis/extra/rediscensus/v8 +``` + +## v8.5 + +- [knadh](https://github.com/knadh) contributed long-awaited ability to scan Redis Hash into a + struct: + +```go +err := rdb.HGetAll(ctx, "hash").Scan(&data) + +err := rdb.MGet(ctx, "key1", "key2").Scan(&data) +``` + +- Please check [redismock](https://github.com/go-redis/redismock) by + [monkey92t](https://github.com/monkey92t) if you are looking for mocking Redis Client. + +## v8 + +- All commands require `context.Context` as a first argument, e.g. `rdb.Ping(ctx)`. If you are not + using `context.Context` yet, the simplest option is to define global package variable + `var ctx = context.TODO()` and use it when `ctx` is required. + +- Full support for `context.Context` canceling. + +- Added `redis.NewFailoverClusterClient` that supports routing read-only commands to a slave node. + +- Added `redisext.OpenTemetryHook` that adds + [Redis OpenTelemetry instrumentation](https://redis.uptrace.dev/tracing/). + +- Redis slow log support. + +- Ring uses Rendezvous Hashing by default which provides better distribution. You need to move + existing keys to a new location or keys will be inaccessible / lost. To use old hashing scheme: + +```go +import "github.com/golang/groupcache/consistenthash" + +ring := redis.NewRing(&redis.RingOptions{ + NewConsistentHash: func() { + return consistenthash.New(100, crc32.ChecksumIEEE) + }, +}) +``` + +- `ClusterOptions.MaxRedirects` default value is changed from 8 to 3. +- `Options.MaxRetries` default value is changed from 0 to 3. + +- `Cluster.ForEachNode` is renamed to `ForEachShard` for consistency with `Ring`. + +## v7.3 + +- New option `Options.Username` which causes client to use `AuthACL`. Be aware if your connection + URL contains username. + +## v7.2 + +- Existing `HMSet` is renamed to `HSet` and old deprecated `HMSet` is restored for Redis 3 users. + +## v7.1 + +- Existing `Cmd.String` is renamed to `Cmd.Text`. New `Cmd.String` implements `fmt.Stringer` + interface. + +## v7 + +- _Important_. Tx.Pipeline now returns a non-transactional pipeline. Use Tx.TxPipeline for a + transactional pipeline. +- WrapProcess is replaced with more convenient AddHook that has access to context.Context. +- WithContext now can not be used to create a shallow copy of the client. +- New methods ProcessContext, DoContext, and ExecContext. +- Client respects Context.Deadline when setting net.Conn deadline. +- Client listens on Context.Done while waiting for a connection from the pool and returns an error + when context context is cancelled. +- Add PubSub.ChannelWithSubscriptions that sends `*Subscription` in addition to `*Message` to allow + detecting reconnections. +- `time.Time` is now marshalled in RFC3339 format. `rdb.Get("foo").Time()` helper is added to parse + the time. +- `SetLimiter` is removed and added `Options.Limiter` instead. +- `HMSet` is deprecated as of Redis v4. + +## v6.15 + +- Cluster and Ring pipelines process commands for each node in its own goroutine. + +## 6.14 + +- Added Options.MinIdleConns. +- Added Options.MaxConnAge. +- PoolStats.FreeConns is renamed to PoolStats.IdleConns. +- Add Client.Do to simplify creating custom commands. +- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers. +- Lower memory usage. + +## v6.13 + +- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set + `HashReplicas = 1000` for better keys distribution between shards. +- Cluster client was optimized to use much less memory when reloading cluster state. +- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout + occurres. In most cases it is recommended to use PubSub.Channel instead. +- Dialer.KeepAlive is set to 5 minutes by default. + +## v6.12 + +- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis + Servers that don't have cluster mode enabled. See + https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup diff --git a/vendor/github.com/go-redis/redis/v8/LICENSE b/vendor/github.com/go-redis/redis/v8/LICENSE new file mode 100644 index 0000000000..298bed9bea --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2013 The github.com/go-redis/redis Authors. +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 +OWNER 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. diff --git a/vendor/github.com/go-redis/redis/v8/Makefile b/vendor/github.com/go-redis/redis/v8/Makefile new file mode 100644 index 0000000000..a4cfe0576e --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/Makefile @@ -0,0 +1,35 @@ +PACKAGE_DIRS := $(shell find . -mindepth 2 -type f -name 'go.mod' -exec dirname {} \; | sort) + +test: testdeps + go test ./... + go test ./... -short -race + go test ./... -run=NONE -bench=. -benchmem + env GOOS=linux GOARCH=386 go test ./... + go vet + +testdeps: testdata/redis/src/redis-server + +bench: testdeps + go test ./... -test.run=NONE -test.bench=. -test.benchmem + +.PHONY: all test testdeps bench + +testdata/redis: + mkdir -p $@ + wget -qO- https://download.redis.io/releases/redis-6.2.5.tar.gz | tar xvz --strip-components=1 -C $@ + +testdata/redis/src/redis-server: testdata/redis + cd $< && make all + +fmt: + gofmt -w -s ./ + goimports -w -local github.com/go-redis/redis ./ + +go_mod_tidy: + go get -u && go mod tidy + set -e; for dir in $(PACKAGE_DIRS); do \ + echo "go mod tidy in $${dir}"; \ + (cd "$${dir}" && \ + go get -u && \ + go mod tidy); \ + done diff --git a/vendor/github.com/go-redis/redis/v8/README.md b/vendor/github.com/go-redis/redis/v8/README.md new file mode 100644 index 0000000000..f3b6a018cb --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/README.md @@ -0,0 +1,175 @@ +# Redis client for Go + +![build workflow](https://github.com/go-redis/redis/actions/workflows/build.yml/badge.svg) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-redis/redis/v8)](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc) +[![Documentation](https://img.shields.io/badge/redis-documentation-informational)](https://redis.uptrace.dev/) + +go-redis is brought to you by :star: [**uptrace/uptrace**](https://github.com/uptrace/uptrace). +Uptrace is an open source and blazingly fast **distributed tracing** backend powered by +OpenTelemetry and ClickHouse. Give it a star as well! + +## Resources + +- [Discussions](https://github.com/go-redis/redis/discussions) +- [Documentation](https://redis.uptrace.dev) +- [Reference](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc) +- [Examples](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#pkg-examples) +- [RealWorld example app](https://github.com/uptrace/go-treemux-realworld-example-app) + +Other projects you may like: + +- [Bun](https://bun.uptrace.dev) - fast and simple SQL client for PostgreSQL, MySQL, and SQLite. +- [BunRouter](https://bunrouter.uptrace.dev/) - fast and flexible HTTP router for Go. + +## Ecosystem + +- [Redis Mock](https://github.com/go-redis/redismock) +- [Distributed Locks](https://github.com/bsm/redislock) +- [Redis Cache](https://github.com/go-redis/cache) +- [Rate limiting](https://github.com/go-redis/redis_rate) + +## Features + +- Redis 3 commands except QUIT, MONITOR, and SYNC. +- Automatic connection pooling with + [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support. +- [Pub/Sub](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#PubSub). +- [Transactions](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client-TxPipeline). +- [Pipeline](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client.Pipeline) and + [TxPipeline](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-Client.TxPipeline). +- [Scripting](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#Script). +- [Timeouts](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#Options). +- [Redis Sentinel](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewFailoverClient). +- [Redis Cluster](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewClusterClient). +- [Cluster of Redis Servers](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-NewClusterClient-ManualSetup) + without using cluster mode and Redis Sentinel. +- [Ring](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#NewRing). +- [Instrumentation](https://pkg.go.dev/github.com/go-redis/redis/v8?tab=doc#example-package-Instrumentation). + +## Installation + +go-redis supports 2 last Go versions and requires a Go version with +[modules](https://github.com/golang/go/wiki/Modules) support. So make sure to initialize a Go +module: + +```shell +go mod init github.com/my/repo +``` + +And then install go-redis/v8 (note _v8_ in the import; omitting it is a popular mistake): + +```shell +go get github.com/go-redis/redis/v8 +``` + +## Quickstart + +```go +import ( + "context" + "github.com/go-redis/redis/v8" + "fmt" +) + +var ctx = context.Background() + +func ExampleClient() { + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) + + err := rdb.Set(ctx, "key", "value", 0).Err() + if err != nil { + panic(err) + } + + val, err := rdb.Get(ctx, "key").Result() + if err != nil { + panic(err) + } + fmt.Println("key", val) + + val2, err := rdb.Get(ctx, "key2").Result() + if err == redis.Nil { + fmt.Println("key2 does not exist") + } else if err != nil { + panic(err) + } else { + fmt.Println("key2", val2) + } + // Output: key value + // key2 does not exist +} +``` + +## Look and feel + +Some corner cases: + +```go +// SET key value EX 10 NX +set, err := rdb.SetNX(ctx, "key", "value", 10*time.Second).Result() + +// SET key value keepttl NX +set, err := rdb.SetNX(ctx, "key", "value", redis.KeepTTL).Result() + +// SORT list LIMIT 0 2 ASC +vals, err := rdb.Sort(ctx, "list", &redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result() + +// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2 +vals, err := rdb.ZRangeByScoreWithScores(ctx, "zset", &redis.ZRangeBy{ + Min: "-inf", + Max: "+inf", + Offset: 0, + Count: 2, +}).Result() + +// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM +vals, err := rdb.ZInterStore(ctx, "out", &redis.ZStore{ + Keys: []string{"zset1", "zset2"}, + Weights: []int64{2, 3} +}).Result() + +// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello" +vals, err := rdb.Eval(ctx, "return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result() + +// custom command +res, err := rdb.Do(ctx, "set", "key", "value").Result() +``` + +## Run the test + +go-redis will start a redis-server and run the test cases. + +The paths of redis-server bin file and redis config file are defined in `main_test.go`: + +``` +var ( + redisServerBin, _ = filepath.Abs(filepath.Join("testdata", "redis", "src", "redis-server")) + redisServerConf, _ = filepath.Abs(filepath.Join("testdata", "redis", "redis.conf")) +) +``` + +For local testing, you can change the variables to refer to your local files, or create a soft link +to the corresponding folder for redis-server and copy the config file to `testdata/redis/`: + +``` +ln -s /usr/bin/redis-server ./go-redis/testdata/redis/src +cp ./go-redis/testdata/redis.conf ./go-redis/testdata/redis/ +``` + +Lastly, run: + +``` +go test +``` + +## Contributors + +Thanks to all the people who already contributed! + + + + diff --git a/vendor/github.com/go-redis/redis/v8/RELEASING.md b/vendor/github.com/go-redis/redis/v8/RELEASING.md new file mode 100644 index 0000000000..1115db4e3e --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/RELEASING.md @@ -0,0 +1,15 @@ +# Releasing + +1. Run `release.sh` script which updates versions in go.mod files and pushes a new branch to GitHub: + +```shell +TAG=v1.0.0 ./scripts/release.sh +``` + +2. Open a pull request and wait for the build to finish. + +3. Merge the pull request and run `tag.sh` to create tags for packages: + +```shell +TAG=v1.0.0 ./scripts/tag.sh +``` diff --git a/vendor/github.com/go-redis/redis/v8/cluster.go b/vendor/github.com/go-redis/redis/v8/cluster.go new file mode 100644 index 0000000000..a54f2f37ed --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/cluster.go @@ -0,0 +1,1750 @@ +package redis + +import ( + "context" + "crypto/tls" + "fmt" + "math" + "net" + "runtime" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/go-redis/redis/v8/internal" + "github.com/go-redis/redis/v8/internal/hashtag" + "github.com/go-redis/redis/v8/internal/pool" + "github.com/go-redis/redis/v8/internal/proto" + "github.com/go-redis/redis/v8/internal/rand" +) + +var errClusterNoNodes = fmt.Errorf("redis: cluster has no nodes") + +// ClusterOptions are used to configure a cluster client and should be +// passed to NewClusterClient. +type ClusterOptions struct { + // A seed list of host:port addresses of cluster nodes. + Addrs []string + + // NewClient creates a cluster node client with provided name and options. + NewClient func(opt *Options) *Client + + // The maximum number of retries before giving up. Command is retried + // on network errors and MOVED/ASK redirects. + // Default is 3 retries. + MaxRedirects int + + // Enables read-only commands on slave nodes. + ReadOnly bool + // Allows routing read-only commands to the closest master or slave node. + // It automatically enables ReadOnly. + RouteByLatency bool + // Allows routing read-only commands to the random master or slave node. + // It automatically enables ReadOnly. + RouteRandomly bool + + // Optional function that returns cluster slots information. + // It is useful to manually create cluster of standalone Redis servers + // and load-balance read/write operations between master and slaves. + // It can use service like ZooKeeper to maintain configuration information + // and Cluster.ReloadState to manually trigger state reloading. + ClusterSlots func(context.Context) ([]ClusterSlot, error) + + // Following options are copied from Options struct. + + Dialer func(ctx context.Context, network, addr string) (net.Conn, error) + + OnConnect func(ctx context.Context, cn *Conn) error + + Username string + Password string + + MaxRetries int + MinRetryBackoff time.Duration + MaxRetryBackoff time.Duration + + DialTimeout time.Duration + ReadTimeout time.Duration + WriteTimeout time.Duration + + // PoolFIFO uses FIFO mode for each node connection pool GET/PUT (default LIFO). + PoolFIFO bool + + // PoolSize applies per cluster node and not for the whole cluster. + PoolSize int + MinIdleConns int + MaxConnAge time.Duration + PoolTimeout time.Duration + IdleTimeout time.Duration + IdleCheckFrequency time.Duration + + TLSConfig *tls.Config +} + +func (opt *ClusterOptions) init() { + if opt.MaxRedirects == -1 { + opt.MaxRedirects = 0 + } else if opt.MaxRedirects == 0 { + opt.MaxRedirects = 3 + } + + if opt.RouteByLatency || opt.RouteRandomly { + opt.ReadOnly = true + } + + if opt.PoolSize == 0 { + opt.PoolSize = 5 * runtime.GOMAXPROCS(0) + } + + switch opt.ReadTimeout { + case -1: + opt.ReadTimeout = 0 + case 0: + opt.ReadTimeout = 3 * time.Second + } + switch opt.WriteTimeout { + case -1: + opt.WriteTimeout = 0 + case 0: + opt.WriteTimeout = opt.ReadTimeout + } + + if opt.MaxRetries == 0 { + opt.MaxRetries = -1 + } + switch opt.MinRetryBackoff { + case -1: + opt.MinRetryBackoff = 0 + case 0: + opt.MinRetryBackoff = 8 * time.Millisecond + } + switch opt.MaxRetryBackoff { + case -1: + opt.MaxRetryBackoff = 0 + case 0: + opt.MaxRetryBackoff = 512 * time.Millisecond + } + + if opt.NewClient == nil { + opt.NewClient = NewClient + } +} + +func (opt *ClusterOptions) clientOptions() *Options { + const disableIdleCheck = -1 + + return &Options{ + Dialer: opt.Dialer, + OnConnect: opt.OnConnect, + + Username: opt.Username, + Password: opt.Password, + + MaxRetries: opt.MaxRetries, + MinRetryBackoff: opt.MinRetryBackoff, + MaxRetryBackoff: opt.MaxRetryBackoff, + + DialTimeout: opt.DialTimeout, + ReadTimeout: opt.ReadTimeout, + WriteTimeout: opt.WriteTimeout, + + PoolFIFO: opt.PoolFIFO, + PoolSize: opt.PoolSize, + MinIdleConns: opt.MinIdleConns, + MaxConnAge: opt.MaxConnAge, + PoolTimeout: opt.PoolTimeout, + IdleTimeout: opt.IdleTimeout, + IdleCheckFrequency: disableIdleCheck, + + TLSConfig: opt.TLSConfig, + // If ClusterSlots is populated, then we probably have an artificial + // cluster whose nodes are not in clustering mode (otherwise there isn't + // much use for ClusterSlots config). This means we cannot execute the + // READONLY command against that node -- setting readOnly to false in such + // situations in the options below will prevent that from happening. + readOnly: opt.ReadOnly && opt.ClusterSlots == nil, + } +} + +//------------------------------------------------------------------------------ + +type clusterNode struct { + Client *Client + + latency uint32 // atomic + generation uint32 // atomic + failing uint32 // atomic +} + +func newClusterNode(clOpt *ClusterOptions, addr string) *clusterNode { + opt := clOpt.clientOptions() + opt.Addr = addr + node := clusterNode{ + Client: clOpt.NewClient(opt), + } + + node.latency = math.MaxUint32 + if clOpt.RouteByLatency { + go node.updateLatency() + } + + return &node +} + +func (n *clusterNode) String() string { + return n.Client.String() +} + +func (n *clusterNode) Close() error { + return n.Client.Close() +} + +func (n *clusterNode) updateLatency() { + const numProbe = 10 + var dur uint64 + + for i := 0; i < numProbe; i++ { + time.Sleep(time.Duration(10+rand.Intn(10)) * time.Millisecond) + + start := time.Now() + n.Client.Ping(context.TODO()) + dur += uint64(time.Since(start) / time.Microsecond) + } + + latency := float64(dur) / float64(numProbe) + atomic.StoreUint32(&n.latency, uint32(latency+0.5)) +} + +func (n *clusterNode) Latency() time.Duration { + latency := atomic.LoadUint32(&n.latency) + return time.Duration(latency) * time.Microsecond +} + +func (n *clusterNode) MarkAsFailing() { + atomic.StoreUint32(&n.failing, uint32(time.Now().Unix())) +} + +func (n *clusterNode) Failing() bool { + const timeout = 15 // 15 seconds + + failing := atomic.LoadUint32(&n.failing) + if failing == 0 { + return false + } + if time.Now().Unix()-int64(failing) < timeout { + return true + } + atomic.StoreUint32(&n.failing, 0) + return false +} + +func (n *clusterNode) Generation() uint32 { + return atomic.LoadUint32(&n.generation) +} + +func (n *clusterNode) SetGeneration(gen uint32) { + for { + v := atomic.LoadUint32(&n.generation) + if gen < v || atomic.CompareAndSwapUint32(&n.generation, v, gen) { + break + } + } +} + +//------------------------------------------------------------------------------ + +type clusterNodes struct { + opt *ClusterOptions + + mu sync.RWMutex + addrs []string + nodes map[string]*clusterNode + activeAddrs []string + closed bool + + _generation uint32 // atomic +} + +func newClusterNodes(opt *ClusterOptions) *clusterNodes { + return &clusterNodes{ + opt: opt, + + addrs: opt.Addrs, + nodes: make(map[string]*clusterNode), + } +} + +func (c *clusterNodes) Close() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil + } + c.closed = true + + var firstErr error + for _, node := range c.nodes { + if err := node.Client.Close(); err != nil && firstErr == nil { + firstErr = err + } + } + + c.nodes = nil + c.activeAddrs = nil + + return firstErr +} + +func (c *clusterNodes) Addrs() ([]string, error) { + var addrs []string + + c.mu.RLock() + closed := c.closed //nolint:ifshort + if !closed { + if len(c.activeAddrs) > 0 { + addrs = c.activeAddrs + } else { + addrs = c.addrs + } + } + c.mu.RUnlock() + + if closed { + return nil, pool.ErrClosed + } + if len(addrs) == 0 { + return nil, errClusterNoNodes + } + return addrs, nil +} + +func (c *clusterNodes) NextGeneration() uint32 { + return atomic.AddUint32(&c._generation, 1) +} + +// GC removes unused nodes. +func (c *clusterNodes) GC(generation uint32) { + //nolint:prealloc + var collected []*clusterNode + + c.mu.Lock() + + c.activeAddrs = c.activeAddrs[:0] + for addr, node := range c.nodes { + if node.Generation() >= generation { + c.activeAddrs = append(c.activeAddrs, addr) + if c.opt.RouteByLatency { + go node.updateLatency() + } + continue + } + + delete(c.nodes, addr) + collected = append(collected, node) + } + + c.mu.Unlock() + + for _, node := range collected { + _ = node.Client.Close() + } +} + +func (c *clusterNodes) GetOrCreate(addr string) (*clusterNode, error) { + node, err := c.get(addr) + if err != nil { + return nil, err + } + if node != nil { + return node, nil + } + + c.mu.Lock() + defer c.mu.Unlock() + + if c.closed { + return nil, pool.ErrClosed + } + + node, ok := c.nodes[addr] + if ok { + return node, nil + } + + node = newClusterNode(c.opt, addr) + + c.addrs = appendIfNotExists(c.addrs, addr) + c.nodes[addr] = node + + return node, nil +} + +func (c *clusterNodes) get(addr string) (*clusterNode, error) { + var node *clusterNode + var err error + c.mu.RLock() + if c.closed { + err = pool.ErrClosed + } else { + node = c.nodes[addr] + } + c.mu.RUnlock() + return node, err +} + +func (c *clusterNodes) All() ([]*clusterNode, error) { + c.mu.RLock() + defer c.mu.RUnlock() + + if c.closed { + return nil, pool.ErrClosed + } + + cp := make([]*clusterNode, 0, len(c.nodes)) + for _, node := range c.nodes { + cp = append(cp, node) + } + return cp, nil +} + +func (c *clusterNodes) Random() (*clusterNode, error) { + addrs, err := c.Addrs() + if err != nil { + return nil, err + } + + n := rand.Intn(len(addrs)) + return c.GetOrCreate(addrs[n]) +} + +//------------------------------------------------------------------------------ + +type clusterSlot struct { + start, end int + nodes []*clusterNode +} + +type clusterSlotSlice []*clusterSlot + +func (p clusterSlotSlice) Len() int { + return len(p) +} + +func (p clusterSlotSlice) Less(i, j int) bool { + return p[i].start < p[j].start +} + +func (p clusterSlotSlice) Swap(i, j int) { + p[i], p[j] = p[j], p[i] +} + +type clusterState struct { + nodes *clusterNodes + Masters []*clusterNode + Slaves []*clusterNode + + slots []*clusterSlot + + generation uint32 + createdAt time.Time +} + +func newClusterState( + nodes *clusterNodes, slots []ClusterSlot, origin string, +) (*clusterState, error) { + c := clusterState{ + nodes: nodes, + + slots: make([]*clusterSlot, 0, len(slots)), + + generation: nodes.NextGeneration(), + createdAt: time.Now(), + } + + originHost, _, _ := net.SplitHostPort(origin) + isLoopbackOrigin := isLoopback(originHost) + + for _, slot := range slots { + var nodes []*clusterNode + for i, slotNode := range slot.Nodes { + addr := slotNode.Addr + if !isLoopbackOrigin { + addr = replaceLoopbackHost(addr, originHost) + } + + node, err := c.nodes.GetOrCreate(addr) + if err != nil { + return nil, err + } + + node.SetGeneration(c.generation) + nodes = append(nodes, node) + + if i == 0 { + c.Masters = appendUniqueNode(c.Masters, node) + } else { + c.Slaves = appendUniqueNode(c.Slaves, node) + } + } + + c.slots = append(c.slots, &clusterSlot{ + start: slot.Start, + end: slot.End, + nodes: nodes, + }) + } + + sort.Sort(clusterSlotSlice(c.slots)) + + time.AfterFunc(time.Minute, func() { + nodes.GC(c.generation) + }) + + return &c, nil +} + +func replaceLoopbackHost(nodeAddr, originHost string) string { + nodeHost, nodePort, err := net.SplitHostPort(nodeAddr) + if err != nil { + return nodeAddr + } + + nodeIP := net.ParseIP(nodeHost) + if nodeIP == nil { + return nodeAddr + } + + if !nodeIP.IsLoopback() { + return nodeAddr + } + + // Use origin host which is not loopback and node port. + return net.JoinHostPort(originHost, nodePort) +} + +func isLoopback(host string) bool { + ip := net.ParseIP(host) + if ip == nil { + return true + } + return ip.IsLoopback() +} + +func (c *clusterState) slotMasterNode(slot int) (*clusterNode, error) { + nodes := c.slotNodes(slot) + if len(nodes) > 0 { + return nodes[0], nil + } + return c.nodes.Random() +} + +func (c *clusterState) slotSlaveNode(slot int) (*clusterNode, error) { + nodes := c.slotNodes(slot) + switch len(nodes) { + case 0: + return c.nodes.Random() + case 1: + return nodes[0], nil + case 2: + if slave := nodes[1]; !slave.Failing() { + return slave, nil + } + return nodes[0], nil + default: + var slave *clusterNode + for i := 0; i < 10; i++ { + n := rand.Intn(len(nodes)-1) + 1 + slave = nodes[n] + if !slave.Failing() { + return slave, nil + } + } + + // All slaves are loading - use master. + return nodes[0], nil + } +} + +func (c *clusterState) slotClosestNode(slot int) (*clusterNode, error) { + nodes := c.slotNodes(slot) + if len(nodes) == 0 { + return c.nodes.Random() + } + + var node *clusterNode + for _, n := range nodes { + if n.Failing() { + continue + } + if node == nil || n.Latency() < node.Latency() { + node = n + } + } + if node != nil { + return node, nil + } + + // If all nodes are failing - return random node + return c.nodes.Random() +} + +func (c *clusterState) slotRandomNode(slot int) (*clusterNode, error) { + nodes := c.slotNodes(slot) + if len(nodes) == 0 { + return c.nodes.Random() + } + if len(nodes) == 1 { + return nodes[0], nil + } + randomNodes := rand.Perm(len(nodes)) + for _, idx := range randomNodes { + if node := nodes[idx]; !node.Failing() { + return node, nil + } + } + return nodes[randomNodes[0]], nil +} + +func (c *clusterState) slotNodes(slot int) []*clusterNode { + i := sort.Search(len(c.slots), func(i int) bool { + return c.slots[i].end >= slot + }) + if i >= len(c.slots) { + return nil + } + x := c.slots[i] + if slot >= x.start && slot <= x.end { + return x.nodes + } + return nil +} + +//------------------------------------------------------------------------------ + +type clusterStateHolder struct { + load func(ctx context.Context) (*clusterState, error) + + state atomic.Value + reloading uint32 // atomic +} + +func newClusterStateHolder(fn func(ctx context.Context) (*clusterState, error)) *clusterStateHolder { + return &clusterStateHolder{ + load: fn, + } +} + +func (c *clusterStateHolder) Reload(ctx context.Context) (*clusterState, error) { + state, err := c.load(ctx) + if err != nil { + return nil, err + } + c.state.Store(state) + return state, nil +} + +func (c *clusterStateHolder) LazyReload() { + if !atomic.CompareAndSwapUint32(&c.reloading, 0, 1) { + return + } + go func() { + defer atomic.StoreUint32(&c.reloading, 0) + + _, err := c.Reload(context.Background()) + if err != nil { + return + } + time.Sleep(200 * time.Millisecond) + }() +} + +func (c *clusterStateHolder) Get(ctx context.Context) (*clusterState, error) { + v := c.state.Load() + if v == nil { + return c.Reload(ctx) + } + + state := v.(*clusterState) + if time.Since(state.createdAt) > 10*time.Second { + c.LazyReload() + } + return state, nil +} + +func (c *clusterStateHolder) ReloadOrGet(ctx context.Context) (*clusterState, error) { + state, err := c.Reload(ctx) + if err == nil { + return state, nil + } + return c.Get(ctx) +} + +//------------------------------------------------------------------------------ + +type clusterClient struct { + opt *ClusterOptions + nodes *clusterNodes + state *clusterStateHolder //nolint:structcheck + cmdsInfoCache *cmdsInfoCache //nolint:structcheck +} + +// ClusterClient is a Redis Cluster client representing a pool of zero +// or more underlying connections. It's safe for concurrent use by +// multiple goroutines. +type ClusterClient struct { + *clusterClient + cmdable + hooks + ctx context.Context +} + +// NewClusterClient returns a Redis Cluster client as described in +// http://redis.io/topics/cluster-spec. +func NewClusterClient(opt *ClusterOptions) *ClusterClient { + opt.init() + + c := &ClusterClient{ + clusterClient: &clusterClient{ + opt: opt, + nodes: newClusterNodes(opt), + }, + ctx: context.Background(), + } + c.state = newClusterStateHolder(c.loadState) + c.cmdsInfoCache = newCmdsInfoCache(c.cmdsInfo) + c.cmdable = c.Process + + if opt.IdleCheckFrequency > 0 { + go c.reaper(opt.IdleCheckFrequency) + } + + return c +} + +func (c *ClusterClient) Context() context.Context { + return c.ctx +} + +func (c *ClusterClient) WithContext(ctx context.Context) *ClusterClient { + if ctx == nil { + panic("nil context") + } + clone := *c + clone.cmdable = clone.Process + clone.hooks.lock() + clone.ctx = ctx + return &clone +} + +// Options returns read-only Options that were used to create the client. +func (c *ClusterClient) Options() *ClusterOptions { + return c.opt +} + +// ReloadState reloads cluster state. If available it calls ClusterSlots func +// to get cluster slots information. +func (c *ClusterClient) ReloadState(ctx context.Context) { + c.state.LazyReload() +} + +// Close closes the cluster client, releasing any open resources. +// +// It is rare to Close a ClusterClient, as the ClusterClient is meant +// to be long-lived and shared between many goroutines. +func (c *ClusterClient) Close() error { + return c.nodes.Close() +} + +// Do creates a Cmd from the args and processes the cmd. +func (c *ClusterClient) Do(ctx context.Context, args ...interface{}) *Cmd { + cmd := NewCmd(ctx, args...) + _ = c.Process(ctx, cmd) + return cmd +} + +func (c *ClusterClient) Process(ctx context.Context, cmd Cmder) error { + return c.hooks.process(ctx, cmd, c.process) +} + +func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error { + cmdInfo := c.cmdInfo(cmd.Name()) + slot := c.cmdSlot(cmd) + + var node *clusterNode + var ask bool + var lastErr error + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + return err + } + } + + if node == nil { + var err error + node, err = c.cmdNode(ctx, cmdInfo, slot) + if err != nil { + return err + } + } + + if ask { + pipe := node.Client.Pipeline() + _ = pipe.Process(ctx, NewCmd(ctx, "asking")) + _ = pipe.Process(ctx, cmd) + _, lastErr = pipe.Exec(ctx) + _ = pipe.Close() + ask = false + } else { + lastErr = node.Client.Process(ctx, cmd) + } + + // If there is no error - we are done. + if lastErr == nil { + return nil + } + if isReadOnly := isReadOnlyError(lastErr); isReadOnly || lastErr == pool.ErrClosed { + if isReadOnly { + c.state.LazyReload() + } + node = nil + continue + } + + // If slave is loading - pick another node. + if c.opt.ReadOnly && isLoadingError(lastErr) { + node.MarkAsFailing() + node = nil + continue + } + + var moved bool + var addr string + moved, ask, addr = isMovedError(lastErr) + if moved || ask { + c.state.LazyReload() + + var err error + node, err = c.nodes.GetOrCreate(addr) + if err != nil { + return err + } + continue + } + + if shouldRetry(lastErr, cmd.readTimeout() == nil) { + // First retry the same node. + if attempt == 0 { + continue + } + + // Second try another node. + node.MarkAsFailing() + node = nil + continue + } + + return lastErr + } + return lastErr +} + +// ForEachMaster concurrently calls the fn on each master node in the cluster. +// It returns the first error if any. +func (c *ClusterClient) ForEachMaster( + ctx context.Context, + fn func(ctx context.Context, client *Client) error, +) error { + state, err := c.state.ReloadOrGet(ctx) + if err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + + for _, master := range state.Masters { + wg.Add(1) + go func(node *clusterNode) { + defer wg.Done() + err := fn(ctx, node.Client) + if err != nil { + select { + case errCh <- err: + default: + } + } + }(master) + } + + wg.Wait() + + select { + case err := <-errCh: + return err + default: + return nil + } +} + +// ForEachSlave concurrently calls the fn on each slave node in the cluster. +// It returns the first error if any. +func (c *ClusterClient) ForEachSlave( + ctx context.Context, + fn func(ctx context.Context, client *Client) error, +) error { + state, err := c.state.ReloadOrGet(ctx) + if err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + + for _, slave := range state.Slaves { + wg.Add(1) + go func(node *clusterNode) { + defer wg.Done() + err := fn(ctx, node.Client) + if err != nil { + select { + case errCh <- err: + default: + } + } + }(slave) + } + + wg.Wait() + + select { + case err := <-errCh: + return err + default: + return nil + } +} + +// ForEachShard concurrently calls the fn on each known node in the cluster. +// It returns the first error if any. +func (c *ClusterClient) ForEachShard( + ctx context.Context, + fn func(ctx context.Context, client *Client) error, +) error { + state, err := c.state.ReloadOrGet(ctx) + if err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + + worker := func(node *clusterNode) { + defer wg.Done() + err := fn(ctx, node.Client) + if err != nil { + select { + case errCh <- err: + default: + } + } + } + + for _, node := range state.Masters { + wg.Add(1) + go worker(node) + } + for _, node := range state.Slaves { + wg.Add(1) + go worker(node) + } + + wg.Wait() + + select { + case err := <-errCh: + return err + default: + return nil + } +} + +// PoolStats returns accumulated connection pool stats. +func (c *ClusterClient) PoolStats() *PoolStats { + var acc PoolStats + + state, _ := c.state.Get(context.TODO()) + if state == nil { + return &acc + } + + for _, node := range state.Masters { + s := node.Client.connPool.Stats() + acc.Hits += s.Hits + acc.Misses += s.Misses + acc.Timeouts += s.Timeouts + + acc.TotalConns += s.TotalConns + acc.IdleConns += s.IdleConns + acc.StaleConns += s.StaleConns + } + + for _, node := range state.Slaves { + s := node.Client.connPool.Stats() + acc.Hits += s.Hits + acc.Misses += s.Misses + acc.Timeouts += s.Timeouts + + acc.TotalConns += s.TotalConns + acc.IdleConns += s.IdleConns + acc.StaleConns += s.StaleConns + } + + return &acc +} + +func (c *ClusterClient) loadState(ctx context.Context) (*clusterState, error) { + if c.opt.ClusterSlots != nil { + slots, err := c.opt.ClusterSlots(ctx) + if err != nil { + return nil, err + } + return newClusterState(c.nodes, slots, "") + } + + addrs, err := c.nodes.Addrs() + if err != nil { + return nil, err + } + + var firstErr error + + for _, idx := range rand.Perm(len(addrs)) { + addr := addrs[idx] + + node, err := c.nodes.GetOrCreate(addr) + if err != nil { + if firstErr == nil { + firstErr = err + } + continue + } + + slots, err := node.Client.ClusterSlots(ctx).Result() + if err != nil { + if firstErr == nil { + firstErr = err + } + continue + } + + return newClusterState(c.nodes, slots, node.Client.opt.Addr) + } + + /* + * No node is connectable. It's possible that all nodes' IP has changed. + * Clear activeAddrs to let client be able to re-connect using the initial + * setting of the addresses (e.g. [redis-cluster-0:6379, redis-cluster-1:6379]), + * which might have chance to resolve domain name and get updated IP address. + */ + c.nodes.mu.Lock() + c.nodes.activeAddrs = nil + c.nodes.mu.Unlock() + + return nil, firstErr +} + +// reaper closes idle connections to the cluster. +func (c *ClusterClient) reaper(idleCheckFrequency time.Duration) { + ticker := time.NewTicker(idleCheckFrequency) + defer ticker.Stop() + + for range ticker.C { + nodes, err := c.nodes.All() + if err != nil { + break + } + + for _, node := range nodes { + _, err := node.Client.connPool.(*pool.ConnPool).ReapStaleConns() + if err != nil { + internal.Logger.Printf(c.Context(), "ReapStaleConns failed: %s", err) + } + } + } +} + +func (c *ClusterClient) Pipeline() Pipeliner { + pipe := Pipeline{ + ctx: c.ctx, + exec: c.processPipeline, + } + pipe.init() + return &pipe +} + +func (c *ClusterClient) Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) { + return c.Pipeline().Pipelined(ctx, fn) +} + +func (c *ClusterClient) processPipeline(ctx context.Context, cmds []Cmder) error { + return c.hooks.processPipeline(ctx, cmds, c._processPipeline) +} + +func (c *ClusterClient) _processPipeline(ctx context.Context, cmds []Cmder) error { + cmdsMap := newCmdsMap() + err := c.mapCmdsByNode(ctx, cmdsMap, cmds) + if err != nil { + setCmdsErr(cmds, err) + return err + } + + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + setCmdsErr(cmds, err) + return err + } + } + + failedCmds := newCmdsMap() + var wg sync.WaitGroup + + for node, cmds := range cmdsMap.m { + wg.Add(1) + go func(node *clusterNode, cmds []Cmder) { + defer wg.Done() + + err := c._processPipelineNode(ctx, node, cmds, failedCmds) + if err == nil { + return + } + if attempt < c.opt.MaxRedirects { + if err := c.mapCmdsByNode(ctx, failedCmds, cmds); err != nil { + setCmdsErr(cmds, err) + } + } else { + setCmdsErr(cmds, err) + } + }(node, cmds) + } + + wg.Wait() + if len(failedCmds.m) == 0 { + break + } + cmdsMap = failedCmds + } + + return cmdsFirstErr(cmds) +} + +func (c *ClusterClient) mapCmdsByNode(ctx context.Context, cmdsMap *cmdsMap, cmds []Cmder) error { + state, err := c.state.Get(ctx) + if err != nil { + return err + } + + if c.opt.ReadOnly && c.cmdsAreReadOnly(cmds) { + for _, cmd := range cmds { + slot := c.cmdSlot(cmd) + node, err := c.slotReadOnlyNode(state, slot) + if err != nil { + return err + } + cmdsMap.Add(node, cmd) + } + return nil + } + + for _, cmd := range cmds { + slot := c.cmdSlot(cmd) + node, err := state.slotMasterNode(slot) + if err != nil { + return err + } + cmdsMap.Add(node, cmd) + } + return nil +} + +func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool { + for _, cmd := range cmds { + cmdInfo := c.cmdInfo(cmd.Name()) + if cmdInfo == nil || !cmdInfo.ReadOnly { + return false + } + } + return true +} + +func (c *ClusterClient) _processPipelineNode( + ctx context.Context, node *clusterNode, cmds []Cmder, failedCmds *cmdsMap, +) error { + return node.Client.hooks.processPipeline(ctx, cmds, func(ctx context.Context, cmds []Cmder) error { + return node.Client.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error { + err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error { + return writeCmds(wr, cmds) + }) + if err != nil { + return err + } + + return cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error { + return c.pipelineReadCmds(ctx, node, rd, cmds, failedCmds) + }) + }) + }) +} + +func (c *ClusterClient) pipelineReadCmds( + ctx context.Context, + node *clusterNode, + rd *proto.Reader, + cmds []Cmder, + failedCmds *cmdsMap, +) error { + for _, cmd := range cmds { + err := cmd.readReply(rd) + cmd.SetErr(err) + + if err == nil { + continue + } + + if c.checkMovedErr(ctx, cmd, err, failedCmds) { + continue + } + + if c.opt.ReadOnly && isLoadingError(err) { + node.MarkAsFailing() + return err + } + if isRedisError(err) { + continue + } + return err + } + return nil +} + +func (c *ClusterClient) checkMovedErr( + ctx context.Context, cmd Cmder, err error, failedCmds *cmdsMap, +) bool { + moved, ask, addr := isMovedError(err) + if !moved && !ask { + return false + } + + node, err := c.nodes.GetOrCreate(addr) + if err != nil { + return false + } + + if moved { + c.state.LazyReload() + failedCmds.Add(node, cmd) + return true + } + + if ask { + failedCmds.Add(node, NewCmd(ctx, "asking"), cmd) + return true + } + + panic("not reached") +} + +// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC. +func (c *ClusterClient) TxPipeline() Pipeliner { + pipe := Pipeline{ + ctx: c.ctx, + exec: c.processTxPipeline, + } + pipe.init() + return &pipe +} + +func (c *ClusterClient) TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) { + return c.TxPipeline().Pipelined(ctx, fn) +} + +func (c *ClusterClient) processTxPipeline(ctx context.Context, cmds []Cmder) error { + return c.hooks.processTxPipeline(ctx, cmds, c._processTxPipeline) +} + +func (c *ClusterClient) _processTxPipeline(ctx context.Context, cmds []Cmder) error { + // Trim multi .. exec. + cmds = cmds[1 : len(cmds)-1] + + state, err := c.state.Get(ctx) + if err != nil { + setCmdsErr(cmds, err) + return err + } + + cmdsMap := c.mapCmdsBySlot(cmds) + for slot, cmds := range cmdsMap { + node, err := state.slotMasterNode(slot) + if err != nil { + setCmdsErr(cmds, err) + continue + } + + cmdsMap := map[*clusterNode][]Cmder{node: cmds} + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + setCmdsErr(cmds, err) + return err + } + } + + failedCmds := newCmdsMap() + var wg sync.WaitGroup + + for node, cmds := range cmdsMap { + wg.Add(1) + go func(node *clusterNode, cmds []Cmder) { + defer wg.Done() + + err := c._processTxPipelineNode(ctx, node, cmds, failedCmds) + if err == nil { + return + } + + if attempt < c.opt.MaxRedirects { + if err := c.mapCmdsByNode(ctx, failedCmds, cmds); err != nil { + setCmdsErr(cmds, err) + } + } else { + setCmdsErr(cmds, err) + } + }(node, cmds) + } + + wg.Wait() + if len(failedCmds.m) == 0 { + break + } + cmdsMap = failedCmds.m + } + } + + return cmdsFirstErr(cmds) +} + +func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder { + cmdsMap := make(map[int][]Cmder) + for _, cmd := range cmds { + slot := c.cmdSlot(cmd) + cmdsMap[slot] = append(cmdsMap[slot], cmd) + } + return cmdsMap +} + +func (c *ClusterClient) _processTxPipelineNode( + ctx context.Context, node *clusterNode, cmds []Cmder, failedCmds *cmdsMap, +) error { + return node.Client.hooks.processTxPipeline(ctx, cmds, func(ctx context.Context, cmds []Cmder) error { + return node.Client.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error { + err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error { + return writeCmds(wr, cmds) + }) + if err != nil { + return err + } + + return cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error { + statusCmd := cmds[0].(*StatusCmd) + // Trim multi and exec. + cmds = cmds[1 : len(cmds)-1] + + err := c.txPipelineReadQueued(ctx, rd, statusCmd, cmds, failedCmds) + if err != nil { + moved, ask, addr := isMovedError(err) + if moved || ask { + return c.cmdsMoved(ctx, cmds, moved, ask, addr, failedCmds) + } + return err + } + + return pipelineReadCmds(rd, cmds) + }) + }) + }) +} + +func (c *ClusterClient) txPipelineReadQueued( + ctx context.Context, + rd *proto.Reader, + statusCmd *StatusCmd, + cmds []Cmder, + failedCmds *cmdsMap, +) error { + // Parse queued replies. + if err := statusCmd.readReply(rd); err != nil { + return err + } + + for _, cmd := range cmds { + err := statusCmd.readReply(rd) + if err == nil || c.checkMovedErr(ctx, cmd, err, failedCmds) || isRedisError(err) { + continue + } + return err + } + + // Parse number of replies. + line, err := rd.ReadLine() + if err != nil { + if err == Nil { + err = TxFailedErr + } + return err + } + + switch line[0] { + case proto.ErrorReply: + return proto.ParseErrorReply(line) + case proto.ArrayReply: + // ok + default: + return fmt.Errorf("redis: expected '*', but got line %q", line) + } + + return nil +} + +func (c *ClusterClient) cmdsMoved( + ctx context.Context, cmds []Cmder, + moved, ask bool, + addr string, + failedCmds *cmdsMap, +) error { + node, err := c.nodes.GetOrCreate(addr) + if err != nil { + return err + } + + if moved { + c.state.LazyReload() + for _, cmd := range cmds { + failedCmds.Add(node, cmd) + } + return nil + } + + if ask { + for _, cmd := range cmds { + failedCmds.Add(node, NewCmd(ctx, "asking"), cmd) + } + return nil + } + + return nil +} + +func (c *ClusterClient) Watch(ctx context.Context, fn func(*Tx) error, keys ...string) error { + if len(keys) == 0 { + return fmt.Errorf("redis: Watch requires at least one key") + } + + slot := hashtag.Slot(keys[0]) + for _, key := range keys[1:] { + if hashtag.Slot(key) != slot { + err := fmt.Errorf("redis: Watch requires all keys to be in the same slot") + return err + } + } + + node, err := c.slotMasterNode(ctx, slot) + if err != nil { + return err + } + + for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ { + if attempt > 0 { + if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil { + return err + } + } + + err = node.Client.Watch(ctx, fn, keys...) + if err == nil { + break + } + + moved, ask, addr := isMovedError(err) + if moved || ask { + node, err = c.nodes.GetOrCreate(addr) + if err != nil { + return err + } + continue + } + + if isReadOnly := isReadOnlyError(err); isReadOnly || err == pool.ErrClosed { + if isReadOnly { + c.state.LazyReload() + } + node, err = c.slotMasterNode(ctx, slot) + if err != nil { + return err + } + continue + } + + if shouldRetry(err, true) { + continue + } + + return err + } + + return err +} + +func (c *ClusterClient) pubSub() *PubSub { + var node *clusterNode + pubsub := &PubSub{ + opt: c.opt.clientOptions(), + + newConn: func(ctx context.Context, channels []string) (*pool.Conn, error) { + if node != nil { + panic("node != nil") + } + + var err error + if len(channels) > 0 { + slot := hashtag.Slot(channels[0]) + node, err = c.slotMasterNode(ctx, slot) + } else { + node, err = c.nodes.Random() + } + if err != nil { + return nil, err + } + + cn, err := node.Client.newConn(context.TODO()) + if err != nil { + node = nil + + return nil, err + } + + return cn, nil + }, + closeConn: func(cn *pool.Conn) error { + err := node.Client.connPool.CloseConn(cn) + node = nil + return err + }, + } + pubsub.init() + + return pubsub +} + +// Subscribe subscribes the client to the specified channels. +// Channels can be omitted to create empty subscription. +func (c *ClusterClient) Subscribe(ctx context.Context, channels ...string) *PubSub { + pubsub := c.pubSub() + if len(channels) > 0 { + _ = pubsub.Subscribe(ctx, channels...) + } + return pubsub +} + +// PSubscribe subscribes the client to the given patterns. +// Patterns can be omitted to create empty subscription. +func (c *ClusterClient) PSubscribe(ctx context.Context, channels ...string) *PubSub { + pubsub := c.pubSub() + if len(channels) > 0 { + _ = pubsub.PSubscribe(ctx, channels...) + } + return pubsub +} + +func (c *ClusterClient) retryBackoff(attempt int) time.Duration { + return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff) +} + +func (c *ClusterClient) cmdsInfo(ctx context.Context) (map[string]*CommandInfo, error) { + // Try 3 random nodes. + const nodeLimit = 3 + + addrs, err := c.nodes.Addrs() + if err != nil { + return nil, err + } + + var firstErr error + + perm := rand.Perm(len(addrs)) + if len(perm) > nodeLimit { + perm = perm[:nodeLimit] + } + + for _, idx := range perm { + addr := addrs[idx] + + node, err := c.nodes.GetOrCreate(addr) + if err != nil { + if firstErr == nil { + firstErr = err + } + continue + } + + info, err := node.Client.Command(ctx).Result() + if err == nil { + return info, nil + } + if firstErr == nil { + firstErr = err + } + } + + if firstErr == nil { + panic("not reached") + } + return nil, firstErr +} + +func (c *ClusterClient) cmdInfo(name string) *CommandInfo { + cmdsInfo, err := c.cmdsInfoCache.Get(c.ctx) + if err != nil { + return nil + } + + info := cmdsInfo[name] + if info == nil { + internal.Logger.Printf(c.Context(), "info for cmd=%s not found", name) + } + return info +} + +func (c *ClusterClient) cmdSlot(cmd Cmder) int { + args := cmd.Args() + if args[0] == "cluster" && args[1] == "getkeysinslot" { + return args[2].(int) + } + + cmdInfo := c.cmdInfo(cmd.Name()) + return cmdSlot(cmd, cmdFirstKeyPos(cmd, cmdInfo)) +} + +func cmdSlot(cmd Cmder, pos int) int { + if pos == 0 { + return hashtag.RandomSlot() + } + firstKey := cmd.stringArg(pos) + return hashtag.Slot(firstKey) +} + +func (c *ClusterClient) cmdNode( + ctx context.Context, + cmdInfo *CommandInfo, + slot int, +) (*clusterNode, error) { + state, err := c.state.Get(ctx) + if err != nil { + return nil, err + } + + if c.opt.ReadOnly && cmdInfo != nil && cmdInfo.ReadOnly { + return c.slotReadOnlyNode(state, slot) + } + return state.slotMasterNode(slot) +} + +func (c *clusterClient) slotReadOnlyNode(state *clusterState, slot int) (*clusterNode, error) { + if c.opt.RouteByLatency { + return state.slotClosestNode(slot) + } + if c.opt.RouteRandomly { + return state.slotRandomNode(slot) + } + return state.slotSlaveNode(slot) +} + +func (c *ClusterClient) slotMasterNode(ctx context.Context, slot int) (*clusterNode, error) { + state, err := c.state.Get(ctx) + if err != nil { + return nil, err + } + return state.slotMasterNode(slot) +} + +// SlaveForKey gets a client for a replica node to run any command on it. +// This is especially useful if we want to run a particular lua script which has +// only read only commands on the replica. +// This is because other redis commands generally have a flag that points that +// they are read only and automatically run on the replica nodes +// if ClusterOptions.ReadOnly flag is set to true. +func (c *ClusterClient) SlaveForKey(ctx context.Context, key string) (*Client, error) { + state, err := c.state.Get(ctx) + if err != nil { + return nil, err + } + slot := hashtag.Slot(key) + node, err := c.slotReadOnlyNode(state, slot) + if err != nil { + return nil, err + } + return node.Client, err +} + +// MasterForKey return a client to the master node for a particular key. +func (c *ClusterClient) MasterForKey(ctx context.Context, key string) (*Client, error) { + slot := hashtag.Slot(key) + node, err := c.slotMasterNode(ctx, slot) + if err != nil { + return nil, err + } + return node.Client, err +} + +func appendUniqueNode(nodes []*clusterNode, node *clusterNode) []*clusterNode { + for _, n := range nodes { + if n == node { + return nodes + } + } + return append(nodes, node) +} + +func appendIfNotExists(ss []string, es ...string) []string { +loop: + for _, e := range es { + for _, s := range ss { + if s == e { + continue loop + } + } + ss = append(ss, e) + } + return ss +} + +//------------------------------------------------------------------------------ + +type cmdsMap struct { + mu sync.Mutex + m map[*clusterNode][]Cmder +} + +func newCmdsMap() *cmdsMap { + return &cmdsMap{ + m: make(map[*clusterNode][]Cmder), + } +} + +func (m *cmdsMap) Add(node *clusterNode, cmds ...Cmder) { + m.mu.Lock() + m.m[node] = append(m.m[node], cmds...) + m.mu.Unlock() +} diff --git a/vendor/github.com/go-redis/redis/v8/cluster_commands.go b/vendor/github.com/go-redis/redis/v8/cluster_commands.go new file mode 100644 index 0000000000..085bce83d5 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/cluster_commands.go @@ -0,0 +1,109 @@ +package redis + +import ( + "context" + "sync" + "sync/atomic" +) + +func (c *ClusterClient) DBSize(ctx context.Context) *IntCmd { + cmd := NewIntCmd(ctx, "dbsize") + _ = c.hooks.process(ctx, cmd, func(ctx context.Context, _ Cmder) error { + var size int64 + err := c.ForEachMaster(ctx, func(ctx context.Context, master *Client) error { + n, err := master.DBSize(ctx).Result() + if err != nil { + return err + } + atomic.AddInt64(&size, n) + return nil + }) + if err != nil { + cmd.SetErr(err) + } else { + cmd.val = size + } + return nil + }) + return cmd +} + +func (c *ClusterClient) ScriptLoad(ctx context.Context, script string) *StringCmd { + cmd := NewStringCmd(ctx, "script", "load", script) + _ = c.hooks.process(ctx, cmd, func(ctx context.Context, _ Cmder) error { + mu := &sync.Mutex{} + err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error { + val, err := shard.ScriptLoad(ctx, script).Result() + if err != nil { + return err + } + + mu.Lock() + if cmd.Val() == "" { + cmd.val = val + } + mu.Unlock() + + return nil + }) + if err != nil { + cmd.SetErr(err) + } + return nil + }) + return cmd +} + +func (c *ClusterClient) ScriptFlush(ctx context.Context) *StatusCmd { + cmd := NewStatusCmd(ctx, "script", "flush") + _ = c.hooks.process(ctx, cmd, func(ctx context.Context, _ Cmder) error { + err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error { + return shard.ScriptFlush(ctx).Err() + }) + if err != nil { + cmd.SetErr(err) + } + return nil + }) + return cmd +} + +func (c *ClusterClient) ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd { + args := make([]interface{}, 2+len(hashes)) + args[0] = "script" + args[1] = "exists" + for i, hash := range hashes { + args[2+i] = hash + } + cmd := NewBoolSliceCmd(ctx, args...) + + result := make([]bool, len(hashes)) + for i := range result { + result[i] = true + } + + _ = c.hooks.process(ctx, cmd, func(ctx context.Context, _ Cmder) error { + mu := &sync.Mutex{} + err := c.ForEachShard(ctx, func(ctx context.Context, shard *Client) error { + val, err := shard.ScriptExists(ctx, hashes...).Result() + if err != nil { + return err + } + + mu.Lock() + for i, v := range val { + result[i] = result[i] && v + } + mu.Unlock() + + return nil + }) + if err != nil { + cmd.SetErr(err) + } else { + cmd.val = result + } + return nil + }) + return cmd +} diff --git a/vendor/github.com/go-redis/redis/v8/command.go b/vendor/github.com/go-redis/redis/v8/command.go new file mode 100644 index 0000000000..4bb12a85be --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/command.go @@ -0,0 +1,3478 @@ +package redis + +import ( + "context" + "fmt" + "net" + "strconv" + "time" + + "github.com/go-redis/redis/v8/internal" + "github.com/go-redis/redis/v8/internal/hscan" + "github.com/go-redis/redis/v8/internal/proto" + "github.com/go-redis/redis/v8/internal/util" +) + +type Cmder interface { + Name() string + FullName() string + Args() []interface{} + String() string + stringArg(int) string + firstKeyPos() int8 + SetFirstKeyPos(int8) + + readTimeout() *time.Duration + readReply(rd *proto.Reader) error + + SetErr(error) + Err() error +} + +func setCmdsErr(cmds []Cmder, e error) { + for _, cmd := range cmds { + if cmd.Err() == nil { + cmd.SetErr(e) + } + } +} + +func cmdsFirstErr(cmds []Cmder) error { + for _, cmd := range cmds { + if err := cmd.Err(); err != nil { + return err + } + } + return nil +} + +func writeCmds(wr *proto.Writer, cmds []Cmder) error { + for _, cmd := range cmds { + if err := writeCmd(wr, cmd); err != nil { + return err + } + } + return nil +} + +func writeCmd(wr *proto.Writer, cmd Cmder) error { + return wr.WriteArgs(cmd.Args()) +} + +func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int { + if pos := cmd.firstKeyPos(); pos != 0 { + return int(pos) + } + + switch cmd.Name() { + case "eval", "evalsha": + if cmd.stringArg(2) != "0" { + return 3 + } + + return 0 + case "publish": + return 1 + case "memory": + // https://github.com/redis/redis/issues/7493 + if cmd.stringArg(1) == "usage" { + return 2 + } + } + + if info != nil { + return int(info.FirstKeyPos) + } + return 0 +} + +func cmdString(cmd Cmder, val interface{}) string { + b := make([]byte, 0, 64) + + for i, arg := range cmd.Args() { + if i > 0 { + b = append(b, ' ') + } + b = internal.AppendArg(b, arg) + } + + if err := cmd.Err(); err != nil { + b = append(b, ": "...) + b = append(b, err.Error()...) + } else if val != nil { + b = append(b, ": "...) + b = internal.AppendArg(b, val) + } + + return internal.String(b) +} + +//------------------------------------------------------------------------------ + +type baseCmd struct { + ctx context.Context + args []interface{} + err error + keyPos int8 + + _readTimeout *time.Duration +} + +var _ Cmder = (*Cmd)(nil) + +func (cmd *baseCmd) Name() string { + if len(cmd.args) == 0 { + return "" + } + // Cmd name must be lower cased. + return internal.ToLower(cmd.stringArg(0)) +} + +func (cmd *baseCmd) FullName() string { + switch name := cmd.Name(); name { + case "cluster", "command": + if len(cmd.args) == 1 { + return name + } + if s2, ok := cmd.args[1].(string); ok { + return name + " " + s2 + } + return name + default: + return name + } +} + +func (cmd *baseCmd) Args() []interface{} { + return cmd.args +} + +func (cmd *baseCmd) stringArg(pos int) string { + if pos < 0 || pos >= len(cmd.args) { + return "" + } + arg := cmd.args[pos] + switch v := arg.(type) { + case string: + return v + default: + // TODO: consider using appendArg + return fmt.Sprint(v) + } +} + +func (cmd *baseCmd) firstKeyPos() int8 { + return cmd.keyPos +} + +func (cmd *baseCmd) SetFirstKeyPos(keyPos int8) { + cmd.keyPos = keyPos +} + +func (cmd *baseCmd) SetErr(e error) { + cmd.err = e +} + +func (cmd *baseCmd) Err() error { + return cmd.err +} + +func (cmd *baseCmd) readTimeout() *time.Duration { + return cmd._readTimeout +} + +func (cmd *baseCmd) setReadTimeout(d time.Duration) { + cmd._readTimeout = &d +} + +//------------------------------------------------------------------------------ + +type Cmd struct { + baseCmd + + val interface{} +} + +func NewCmd(ctx context.Context, args ...interface{}) *Cmd { + return &Cmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *Cmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *Cmd) SetVal(val interface{}) { + cmd.val = val +} + +func (cmd *Cmd) Val() interface{} { + return cmd.val +} + +func (cmd *Cmd) Result() (interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *Cmd) Text() (string, error) { + if cmd.err != nil { + return "", cmd.err + } + return toString(cmd.val) +} + +func toString(val interface{}) (string, error) { + switch val := val.(type) { + case string: + return val, nil + default: + err := fmt.Errorf("redis: unexpected type=%T for String", val) + return "", err + } +} + +func (cmd *Cmd) Int() (int, error) { + if cmd.err != nil { + return 0, cmd.err + } + switch val := cmd.val.(type) { + case int64: + return int(val), nil + case string: + return strconv.Atoi(val) + default: + err := fmt.Errorf("redis: unexpected type=%T for Int", val) + return 0, err + } +} + +func (cmd *Cmd) Int64() (int64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return toInt64(cmd.val) +} + +func toInt64(val interface{}) (int64, error) { + switch val := val.(type) { + case int64: + return val, nil + case string: + return strconv.ParseInt(val, 10, 64) + default: + err := fmt.Errorf("redis: unexpected type=%T for Int64", val) + return 0, err + } +} + +func (cmd *Cmd) Uint64() (uint64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return toUint64(cmd.val) +} + +func toUint64(val interface{}) (uint64, error) { + switch val := val.(type) { + case int64: + return uint64(val), nil + case string: + return strconv.ParseUint(val, 10, 64) + default: + err := fmt.Errorf("redis: unexpected type=%T for Uint64", val) + return 0, err + } +} + +func (cmd *Cmd) Float32() (float32, error) { + if cmd.err != nil { + return 0, cmd.err + } + return toFloat32(cmd.val) +} + +func toFloat32(val interface{}) (float32, error) { + switch val := val.(type) { + case int64: + return float32(val), nil + case string: + f, err := strconv.ParseFloat(val, 32) + if err != nil { + return 0, err + } + return float32(f), nil + default: + err := fmt.Errorf("redis: unexpected type=%T for Float32", val) + return 0, err + } +} + +func (cmd *Cmd) Float64() (float64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return toFloat64(cmd.val) +} + +func toFloat64(val interface{}) (float64, error) { + switch val := val.(type) { + case int64: + return float64(val), nil + case string: + return strconv.ParseFloat(val, 64) + default: + err := fmt.Errorf("redis: unexpected type=%T for Float64", val) + return 0, err + } +} + +func (cmd *Cmd) Bool() (bool, error) { + if cmd.err != nil { + return false, cmd.err + } + return toBool(cmd.val) +} + +func toBool(val interface{}) (bool, error) { + switch val := val.(type) { + case int64: + return val != 0, nil + case string: + return strconv.ParseBool(val) + default: + err := fmt.Errorf("redis: unexpected type=%T for Bool", val) + return false, err + } +} + +func (cmd *Cmd) Slice() ([]interface{}, error) { + if cmd.err != nil { + return nil, cmd.err + } + switch val := cmd.val.(type) { + case []interface{}: + return val, nil + default: + return nil, fmt.Errorf("redis: unexpected type=%T for Slice", val) + } +} + +func (cmd *Cmd) StringSlice() ([]string, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + ss := make([]string, len(slice)) + for i, iface := range slice { + val, err := toString(iface) + if err != nil { + return nil, err + } + ss[i] = val + } + return ss, nil +} + +func (cmd *Cmd) Int64Slice() ([]int64, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + nums := make([]int64, len(slice)) + for i, iface := range slice { + val, err := toInt64(iface) + if err != nil { + return nil, err + } + nums[i] = val + } + return nums, nil +} + +func (cmd *Cmd) Uint64Slice() ([]uint64, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + nums := make([]uint64, len(slice)) + for i, iface := range slice { + val, err := toUint64(iface) + if err != nil { + return nil, err + } + nums[i] = val + } + return nums, nil +} + +func (cmd *Cmd) Float32Slice() ([]float32, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + floats := make([]float32, len(slice)) + for i, iface := range slice { + val, err := toFloat32(iface) + if err != nil { + return nil, err + } + floats[i] = val + } + return floats, nil +} + +func (cmd *Cmd) Float64Slice() ([]float64, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + floats := make([]float64, len(slice)) + for i, iface := range slice { + val, err := toFloat64(iface) + if err != nil { + return nil, err + } + floats[i] = val + } + return floats, nil +} + +func (cmd *Cmd) BoolSlice() ([]bool, error) { + slice, err := cmd.Slice() + if err != nil { + return nil, err + } + + bools := make([]bool, len(slice)) + for i, iface := range slice { + val, err := toBool(iface) + if err != nil { + return nil, err + } + bools[i] = val + } + return bools, nil +} + +func (cmd *Cmd) readReply(rd *proto.Reader) (err error) { + cmd.val, err = rd.ReadReply(sliceParser) + return err +} + +// sliceParser implements proto.MultiBulkParse. +func sliceParser(rd *proto.Reader, n int64) (interface{}, error) { + vals := make([]interface{}, n) + for i := 0; i < len(vals); i++ { + v, err := rd.ReadReply(sliceParser) + if err != nil { + if err == Nil { + vals[i] = nil + continue + } + if err, ok := err.(proto.RedisError); ok { + vals[i] = err + continue + } + return nil, err + } + vals[i] = v + } + return vals, nil +} + +//------------------------------------------------------------------------------ + +type SliceCmd struct { + baseCmd + + val []interface{} +} + +var _ Cmder = (*SliceCmd)(nil) + +func NewSliceCmd(ctx context.Context, args ...interface{}) *SliceCmd { + return &SliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *SliceCmd) SetVal(val []interface{}) { + cmd.val = val +} + +func (cmd *SliceCmd) Val() []interface{} { + return cmd.val +} + +func (cmd *SliceCmd) Result() ([]interface{}, error) { + return cmd.val, cmd.err +} + +func (cmd *SliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +// Scan scans the results from the map into a destination struct. The map keys +// are matched in the Redis struct fields by the `redis:"field"` tag. +func (cmd *SliceCmd) Scan(dst interface{}) error { + if cmd.err != nil { + return cmd.err + } + + // Pass the list of keys and values. + // Skip the first two args for: HMGET key + var args []interface{} + if cmd.args[0] == "hmget" { + args = cmd.args[2:] + } else { + // Otherwise, it's: MGET field field ... + args = cmd.args[1:] + } + + return hscan.Scan(dst, args, cmd.val) +} + +func (cmd *SliceCmd) readReply(rd *proto.Reader) error { + v, err := rd.ReadArrayReply(sliceParser) + if err != nil { + return err + } + cmd.val = v.([]interface{}) + return nil +} + +//------------------------------------------------------------------------------ + +type StatusCmd struct { + baseCmd + + val string +} + +var _ Cmder = (*StatusCmd)(nil) + +func NewStatusCmd(ctx context.Context, args ...interface{}) *StatusCmd { + return &StatusCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *StatusCmd) SetVal(val string) { + cmd.val = val +} + +func (cmd *StatusCmd) Val() string { + return cmd.val +} + +func (cmd *StatusCmd) Result() (string, error) { + return cmd.val, cmd.err +} + +func (cmd *StatusCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StatusCmd) readReply(rd *proto.Reader) (err error) { + cmd.val, err = rd.ReadString() + return err +} + +//------------------------------------------------------------------------------ + +type IntCmd struct { + baseCmd + + val int64 +} + +var _ Cmder = (*IntCmd)(nil) + +func NewIntCmd(ctx context.Context, args ...interface{}) *IntCmd { + return &IntCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *IntCmd) SetVal(val int64) { + cmd.val = val +} + +func (cmd *IntCmd) Val() int64 { + return cmd.val +} + +func (cmd *IntCmd) Result() (int64, error) { + return cmd.val, cmd.err +} + +func (cmd *IntCmd) Uint64() (uint64, error) { + return uint64(cmd.val), cmd.err +} + +func (cmd *IntCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *IntCmd) readReply(rd *proto.Reader) (err error) { + cmd.val, err = rd.ReadIntReply() + return err +} + +//------------------------------------------------------------------------------ + +type IntSliceCmd struct { + baseCmd + + val []int64 +} + +var _ Cmder = (*IntSliceCmd)(nil) + +func NewIntSliceCmd(ctx context.Context, args ...interface{}) *IntSliceCmd { + return &IntSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *IntSliceCmd) SetVal(val []int64) { + cmd.val = val +} + +func (cmd *IntSliceCmd) Val() []int64 { + return cmd.val +} + +func (cmd *IntSliceCmd) Result() ([]int64, error) { + return cmd.val, cmd.err +} + +func (cmd *IntSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *IntSliceCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]int64, n) + for i := 0; i < len(cmd.val); i++ { + num, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.val[i] = num + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type DurationCmd struct { + baseCmd + + val time.Duration + precision time.Duration +} + +var _ Cmder = (*DurationCmd)(nil) + +func NewDurationCmd(ctx context.Context, precision time.Duration, args ...interface{}) *DurationCmd { + return &DurationCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + precision: precision, + } +} + +func (cmd *DurationCmd) SetVal(val time.Duration) { + cmd.val = val +} + +func (cmd *DurationCmd) Val() time.Duration { + return cmd.val +} + +func (cmd *DurationCmd) Result() (time.Duration, error) { + return cmd.val, cmd.err +} + +func (cmd *DurationCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *DurationCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadIntReply() + if err != nil { + return err + } + switch n { + // -2 if the key does not exist + // -1 if the key exists but has no associated expire + case -2, -1: + cmd.val = time.Duration(n) + default: + cmd.val = time.Duration(n) * cmd.precision + } + return nil +} + +//------------------------------------------------------------------------------ + +type TimeCmd struct { + baseCmd + + val time.Time +} + +var _ Cmder = (*TimeCmd)(nil) + +func NewTimeCmd(ctx context.Context, args ...interface{}) *TimeCmd { + return &TimeCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *TimeCmd) SetVal(val time.Time) { + cmd.val = val +} + +func (cmd *TimeCmd) Val() time.Time { + return cmd.val +} + +func (cmd *TimeCmd) Result() (time.Time, error) { + return cmd.val, cmd.err +} + +func (cmd *TimeCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *TimeCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d elements, expected 2", n) + } + + sec, err := rd.ReadInt() + if err != nil { + return nil, err + } + + microsec, err := rd.ReadInt() + if err != nil { + return nil, err + } + + cmd.val = time.Unix(sec, microsec*1000) + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type BoolCmd struct { + baseCmd + + val bool +} + +var _ Cmder = (*BoolCmd)(nil) + +func NewBoolCmd(ctx context.Context, args ...interface{}) *BoolCmd { + return &BoolCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *BoolCmd) SetVal(val bool) { + cmd.val = val +} + +func (cmd *BoolCmd) Val() bool { + return cmd.val +} + +func (cmd *BoolCmd) Result() (bool, error) { + return cmd.val, cmd.err +} + +func (cmd *BoolCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *BoolCmd) readReply(rd *proto.Reader) error { + v, err := rd.ReadReply(nil) + // `SET key value NX` returns nil when key already exists. But + // `SETNX key value` returns bool (0/1). So convert nil to bool. + if err == Nil { + cmd.val = false + return nil + } + if err != nil { + return err + } + switch v := v.(type) { + case int64: + cmd.val = v == 1 + return nil + case string: + cmd.val = v == "OK" + return nil + default: + return fmt.Errorf("got %T, wanted int64 or string", v) + } +} + +//------------------------------------------------------------------------------ + +type StringCmd struct { + baseCmd + + val string +} + +var _ Cmder = (*StringCmd)(nil) + +func NewStringCmd(ctx context.Context, args ...interface{}) *StringCmd { + return &StringCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *StringCmd) SetVal(val string) { + cmd.val = val +} + +func (cmd *StringCmd) Val() string { + return cmd.val +} + +func (cmd *StringCmd) Result() (string, error) { + return cmd.Val(), cmd.err +} + +func (cmd *StringCmd) Bytes() ([]byte, error) { + return util.StringToBytes(cmd.val), cmd.err +} + +func (cmd *StringCmd) Bool() (bool, error) { + if cmd.err != nil { + return false, cmd.err + } + return strconv.ParseBool(cmd.val) +} + +func (cmd *StringCmd) Int() (int, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.Atoi(cmd.Val()) +} + +func (cmd *StringCmd) Int64() (int64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseInt(cmd.Val(), 10, 64) +} + +func (cmd *StringCmd) Uint64() (uint64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseUint(cmd.Val(), 10, 64) +} + +func (cmd *StringCmd) Float32() (float32, error) { + if cmd.err != nil { + return 0, cmd.err + } + f, err := strconv.ParseFloat(cmd.Val(), 32) + if err != nil { + return 0, err + } + return float32(f), nil +} + +func (cmd *StringCmd) Float64() (float64, error) { + if cmd.err != nil { + return 0, cmd.err + } + return strconv.ParseFloat(cmd.Val(), 64) +} + +func (cmd *StringCmd) Time() (time.Time, error) { + if cmd.err != nil { + return time.Time{}, cmd.err + } + return time.Parse(time.RFC3339Nano, cmd.Val()) +} + +func (cmd *StringCmd) Scan(val interface{}) error { + if cmd.err != nil { + return cmd.err + } + return proto.Scan([]byte(cmd.val), val) +} + +func (cmd *StringCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringCmd) readReply(rd *proto.Reader) (err error) { + cmd.val, err = rd.ReadString() + return err +} + +//------------------------------------------------------------------------------ + +type FloatCmd struct { + baseCmd + + val float64 +} + +var _ Cmder = (*FloatCmd)(nil) + +func NewFloatCmd(ctx context.Context, args ...interface{}) *FloatCmd { + return &FloatCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *FloatCmd) SetVal(val float64) { + cmd.val = val +} + +func (cmd *FloatCmd) Val() float64 { + return cmd.val +} + +func (cmd *FloatCmd) Result() (float64, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *FloatCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *FloatCmd) readReply(rd *proto.Reader) (err error) { + cmd.val, err = rd.ReadFloatReply() + return err +} + +//------------------------------------------------------------------------------ + +type FloatSliceCmd struct { + baseCmd + + val []float64 +} + +var _ Cmder = (*FloatSliceCmd)(nil) + +func NewFloatSliceCmd(ctx context.Context, args ...interface{}) *FloatSliceCmd { + return &FloatSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *FloatSliceCmd) SetVal(val []float64) { + cmd.val = val +} + +func (cmd *FloatSliceCmd) Val() []float64 { + return cmd.val +} + +func (cmd *FloatSliceCmd) Result() ([]float64, error) { + return cmd.val, cmd.err +} + +func (cmd *FloatSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *FloatSliceCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]float64, n) + for i := 0; i < len(cmd.val); i++ { + switch num, err := rd.ReadFloatReply(); { + case err == Nil: + cmd.val[i] = 0 + case err != nil: + return nil, err + default: + cmd.val[i] = num + } + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type StringSliceCmd struct { + baseCmd + + val []string +} + +var _ Cmder = (*StringSliceCmd)(nil) + +func NewStringSliceCmd(ctx context.Context, args ...interface{}) *StringSliceCmd { + return &StringSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *StringSliceCmd) SetVal(val []string) { + cmd.val = val +} + +func (cmd *StringSliceCmd) Val() []string { + return cmd.val +} + +func (cmd *StringSliceCmd) Result() ([]string, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *StringSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringSliceCmd) ScanSlice(container interface{}) error { + return proto.ScanSlice(cmd.Val(), container) +} + +func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]string, n) + for i := 0; i < len(cmd.val); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.val[i] = "" + case err != nil: + return nil, err + default: + cmd.val[i] = s + } + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type BoolSliceCmd struct { + baseCmd + + val []bool +} + +var _ Cmder = (*BoolSliceCmd)(nil) + +func NewBoolSliceCmd(ctx context.Context, args ...interface{}) *BoolSliceCmd { + return &BoolSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *BoolSliceCmd) SetVal(val []bool) { + cmd.val = val +} + +func (cmd *BoolSliceCmd) Val() []bool { + return cmd.val +} + +func (cmd *BoolSliceCmd) Result() ([]bool, error) { + return cmd.val, cmd.err +} + +func (cmd *BoolSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]bool, n) + for i := 0; i < len(cmd.val); i++ { + n, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.val[i] = n == 1 + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type StringStringMapCmd struct { + baseCmd + + val map[string]string +} + +var _ Cmder = (*StringStringMapCmd)(nil) + +func NewStringStringMapCmd(ctx context.Context, args ...interface{}) *StringStringMapCmd { + return &StringStringMapCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *StringStringMapCmd) SetVal(val map[string]string) { + cmd.val = val +} + +func (cmd *StringStringMapCmd) Val() map[string]string { + return cmd.val +} + +func (cmd *StringStringMapCmd) Result() (map[string]string, error) { + return cmd.val, cmd.err +} + +func (cmd *StringStringMapCmd) String() string { + return cmdString(cmd, cmd.val) +} + +// Scan scans the results from the map into a destination struct. The map keys +// are matched in the Redis struct fields by the `redis:"field"` tag. +func (cmd *StringStringMapCmd) Scan(dest interface{}) error { + if cmd.err != nil { + return cmd.err + } + + strct, err := hscan.Struct(dest) + if err != nil { + return err + } + + for k, v := range cmd.val { + if err := strct.Scan(k, v); err != nil { + return err + } + } + + return nil +} + +func (cmd *StringStringMapCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]string, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + + value, err := rd.ReadString() + if err != nil { + return nil, err + } + + cmd.val[key] = value + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type StringIntMapCmd struct { + baseCmd + + val map[string]int64 +} + +var _ Cmder = (*StringIntMapCmd)(nil) + +func NewStringIntMapCmd(ctx context.Context, args ...interface{}) *StringIntMapCmd { + return &StringIntMapCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *StringIntMapCmd) SetVal(val map[string]int64) { + cmd.val = val +} + +func (cmd *StringIntMapCmd) Val() map[string]int64 { + return cmd.val +} + +func (cmd *StringIntMapCmd) Result() (map[string]int64, error) { + return cmd.val, cmd.err +} + +func (cmd *StringIntMapCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringIntMapCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]int64, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + + n, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + cmd.val[key] = n + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type StringStructMapCmd struct { + baseCmd + + val map[string]struct{} +} + +var _ Cmder = (*StringStructMapCmd)(nil) + +func NewStringStructMapCmd(ctx context.Context, args ...interface{}) *StringStructMapCmd { + return &StringStructMapCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *StringStructMapCmd) SetVal(val map[string]struct{}) { + cmd.val = val +} + +func (cmd *StringStructMapCmd) Val() map[string]struct{} { + return cmd.val +} + +func (cmd *StringStructMapCmd) Result() (map[string]struct{}, error) { + return cmd.val, cmd.err +} + +func (cmd *StringStructMapCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *StringStructMapCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]struct{}, n) + for i := int64(0); i < n; i++ { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + cmd.val[key] = struct{}{} + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type XMessage struct { + ID string + Values map[string]interface{} +} + +type XMessageSliceCmd struct { + baseCmd + + val []XMessage +} + +var _ Cmder = (*XMessageSliceCmd)(nil) + +func NewXMessageSliceCmd(ctx context.Context, args ...interface{}) *XMessageSliceCmd { + return &XMessageSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XMessageSliceCmd) SetVal(val []XMessage) { + cmd.val = val +} + +func (cmd *XMessageSliceCmd) Val() []XMessage { + return cmd.val +} + +func (cmd *XMessageSliceCmd) Result() ([]XMessage, error) { + return cmd.val, cmd.err +} + +func (cmd *XMessageSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XMessageSliceCmd) readReply(rd *proto.Reader) error { + var err error + cmd.val, err = readXMessageSlice(rd) + return err +} + +func readXMessageSlice(rd *proto.Reader) ([]XMessage, error) { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + + msgs := make([]XMessage, n) + for i := 0; i < n; i++ { + var err error + msgs[i], err = readXMessage(rd) + if err != nil { + return nil, err + } + } + return msgs, nil +} + +func readXMessage(rd *proto.Reader) (XMessage, error) { + n, err := rd.ReadArrayLen() + if err != nil { + return XMessage{}, err + } + if n != 2 { + return XMessage{}, fmt.Errorf("got %d, wanted 2", n) + } + + id, err := rd.ReadString() + if err != nil { + return XMessage{}, err + } + + var values map[string]interface{} + + v, err := rd.ReadArrayReply(stringInterfaceMapParser) + if err != nil { + if err != proto.Nil { + return XMessage{}, err + } + } else { + values = v.(map[string]interface{}) + } + + return XMessage{ + ID: id, + Values: values, + }, nil +} + +// stringInterfaceMapParser implements proto.MultiBulkParse. +func stringInterfaceMapParser(rd *proto.Reader, n int64) (interface{}, error) { + m := make(map[string]interface{}, n/2) + for i := int64(0); i < n; i += 2 { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + + value, err := rd.ReadString() + if err != nil { + return nil, err + } + + m[key] = value + } + return m, nil +} + +//------------------------------------------------------------------------------ + +type XStream struct { + Stream string + Messages []XMessage +} + +type XStreamSliceCmd struct { + baseCmd + + val []XStream +} + +var _ Cmder = (*XStreamSliceCmd)(nil) + +func NewXStreamSliceCmd(ctx context.Context, args ...interface{}) *XStreamSliceCmd { + return &XStreamSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XStreamSliceCmd) SetVal(val []XStream) { + cmd.val = val +} + +func (cmd *XStreamSliceCmd) Val() []XStream { + return cmd.val +} + +func (cmd *XStreamSliceCmd) Result() ([]XStream, error) { + return cmd.val, cmd.err +} + +func (cmd *XStreamSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XStreamSliceCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]XStream, n) + for i := 0; i < len(cmd.val); i++ { + i := i + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d, wanted 2", n) + } + + stream, err := rd.ReadString() + if err != nil { + return nil, err + } + + msgs, err := readXMessageSlice(rd) + if err != nil { + return nil, err + } + + cmd.val[i] = XStream{ + Stream: stream, + Messages: msgs, + } + return nil, nil + }) + if err != nil { + return nil, err + } + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type XPending struct { + Count int64 + Lower string + Higher string + Consumers map[string]int64 +} + +type XPendingCmd struct { + baseCmd + val *XPending +} + +var _ Cmder = (*XPendingCmd)(nil) + +func NewXPendingCmd(ctx context.Context, args ...interface{}) *XPendingCmd { + return &XPendingCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XPendingCmd) SetVal(val *XPending) { + cmd.val = val +} + +func (cmd *XPendingCmd) Val() *XPending { + return cmd.val +} + +func (cmd *XPendingCmd) Result() (*XPending, error) { + return cmd.val, cmd.err +} + +func (cmd *XPendingCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XPendingCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 4 { + return nil, fmt.Errorf("got %d, wanted 4", n) + } + + count, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + lower, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + higher, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + cmd.val = &XPending{ + Count: count, + Lower: lower, + Higher: higher, + } + _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + for i := int64(0); i < n; i++ { + _, err = rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d, wanted 2", n) + } + + consumerName, err := rd.ReadString() + if err != nil { + return nil, err + } + + consumerPending, err := rd.ReadInt() + if err != nil { + return nil, err + } + + if cmd.val.Consumers == nil { + cmd.val.Consumers = make(map[string]int64) + } + cmd.val.Consumers[consumerName] = consumerPending + + return nil, nil + }) + if err != nil { + return nil, err + } + } + return nil, nil + }) + if err != nil && err != Nil { + return nil, err + } + + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type XPendingExt struct { + ID string + Consumer string + Idle time.Duration + RetryCount int64 +} + +type XPendingExtCmd struct { + baseCmd + val []XPendingExt +} + +var _ Cmder = (*XPendingExtCmd)(nil) + +func NewXPendingExtCmd(ctx context.Context, args ...interface{}) *XPendingExtCmd { + return &XPendingExtCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XPendingExtCmd) SetVal(val []XPendingExt) { + cmd.val = val +} + +func (cmd *XPendingExtCmd) Val() []XPendingExt { + return cmd.val +} + +func (cmd *XPendingExtCmd) Result() ([]XPendingExt, error) { + return cmd.val, cmd.err +} + +func (cmd *XPendingExtCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XPendingExtCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]XPendingExt, 0, n) + for i := int64(0); i < n; i++ { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 4 { + return nil, fmt.Errorf("got %d, wanted 4", n) + } + + id, err := rd.ReadString() + if err != nil { + return nil, err + } + + consumer, err := rd.ReadString() + if err != nil && err != Nil { + return nil, err + } + + idle, err := rd.ReadIntReply() + if err != nil && err != Nil { + return nil, err + } + + retryCount, err := rd.ReadIntReply() + if err != nil && err != Nil { + return nil, err + } + + cmd.val = append(cmd.val, XPendingExt{ + ID: id, + Consumer: consumer, + Idle: time.Duration(idle) * time.Millisecond, + RetryCount: retryCount, + }) + return nil, nil + }) + if err != nil { + return nil, err + } + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type XAutoClaimCmd struct { + baseCmd + + start string + val []XMessage +} + +var _ Cmder = (*XAutoClaimCmd)(nil) + +func NewXAutoClaimCmd(ctx context.Context, args ...interface{}) *XAutoClaimCmd { + return &XAutoClaimCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XAutoClaimCmd) SetVal(val []XMessage, start string) { + cmd.val = val + cmd.start = start +} + +func (cmd *XAutoClaimCmd) Val() (messages []XMessage, start string) { + return cmd.val, cmd.start +} + +func (cmd *XAutoClaimCmd) Result() (messages []XMessage, start string, err error) { + return cmd.val, cmd.start, cmd.err +} + +func (cmd *XAutoClaimCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XAutoClaimCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d, wanted 2", n) + } + var err error + + cmd.start, err = rd.ReadString() + if err != nil { + return nil, err + } + + cmd.val, err = readXMessageSlice(rd) + if err != nil { + return nil, err + } + + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type XAutoClaimJustIDCmd struct { + baseCmd + + start string + val []string +} + +var _ Cmder = (*XAutoClaimJustIDCmd)(nil) + +func NewXAutoClaimJustIDCmd(ctx context.Context, args ...interface{}) *XAutoClaimJustIDCmd { + return &XAutoClaimJustIDCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XAutoClaimJustIDCmd) SetVal(val []string, start string) { + cmd.val = val + cmd.start = start +} + +func (cmd *XAutoClaimJustIDCmd) Val() (ids []string, start string) { + return cmd.val, cmd.start +} + +func (cmd *XAutoClaimJustIDCmd) Result() (ids []string, start string, err error) { + return cmd.val, cmd.start, cmd.err +} + +func (cmd *XAutoClaimJustIDCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XAutoClaimJustIDCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 2 { + return nil, fmt.Errorf("got %d, wanted 2", n) + } + var err error + + cmd.start, err = rd.ReadString() + if err != nil { + return nil, err + } + + nn, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + + cmd.val = make([]string, nn) + for i := 0; i < nn; i++ { + cmd.val[i], err = rd.ReadString() + if err != nil { + return nil, err + } + } + + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type XInfoConsumersCmd struct { + baseCmd + val []XInfoConsumer +} + +type XInfoConsumer struct { + Name string + Pending int64 + Idle int64 +} + +var _ Cmder = (*XInfoConsumersCmd)(nil) + +func NewXInfoConsumersCmd(ctx context.Context, stream string, group string) *XInfoConsumersCmd { + return &XInfoConsumersCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: []interface{}{"xinfo", "consumers", stream, group}, + }, + } +} + +func (cmd *XInfoConsumersCmd) SetVal(val []XInfoConsumer) { + cmd.val = val +} + +func (cmd *XInfoConsumersCmd) Val() []XInfoConsumer { + return cmd.val +} + +func (cmd *XInfoConsumersCmd) Result() ([]XInfoConsumer, error) { + return cmd.val, cmd.err +} + +func (cmd *XInfoConsumersCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XInfoConsumersCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make([]XInfoConsumer, n) + + for i := 0; i < n; i++ { + cmd.val[i], err = readXConsumerInfo(rd) + if err != nil { + return err + } + } + + return nil +} + +func readXConsumerInfo(rd *proto.Reader) (XInfoConsumer, error) { + var consumer XInfoConsumer + + n, err := rd.ReadArrayLen() + if err != nil { + return consumer, err + } + if n != 6 { + return consumer, fmt.Errorf("redis: got %d elements in XINFO CONSUMERS reply, wanted 6", n) + } + + for i := 0; i < 3; i++ { + key, err := rd.ReadString() + if err != nil { + return consumer, err + } + + val, err := rd.ReadString() + if err != nil { + return consumer, err + } + + switch key { + case "name": + consumer.Name = val + case "pending": + consumer.Pending, err = strconv.ParseInt(val, 0, 64) + if err != nil { + return consumer, err + } + case "idle": + consumer.Idle, err = strconv.ParseInt(val, 0, 64) + if err != nil { + return consumer, err + } + default: + return consumer, fmt.Errorf("redis: unexpected content %s in XINFO CONSUMERS reply", key) + } + } + + return consumer, nil +} + +//------------------------------------------------------------------------------ + +type XInfoGroupsCmd struct { + baseCmd + val []XInfoGroup +} + +type XInfoGroup struct { + Name string + Consumers int64 + Pending int64 + LastDeliveredID string +} + +var _ Cmder = (*XInfoGroupsCmd)(nil) + +func NewXInfoGroupsCmd(ctx context.Context, stream string) *XInfoGroupsCmd { + return &XInfoGroupsCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: []interface{}{"xinfo", "groups", stream}, + }, + } +} + +func (cmd *XInfoGroupsCmd) SetVal(val []XInfoGroup) { + cmd.val = val +} + +func (cmd *XInfoGroupsCmd) Val() []XInfoGroup { + return cmd.val +} + +func (cmd *XInfoGroupsCmd) Result() ([]XInfoGroup, error) { + return cmd.val, cmd.err +} + +func (cmd *XInfoGroupsCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XInfoGroupsCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make([]XInfoGroup, n) + + for i := 0; i < n; i++ { + cmd.val[i], err = readXGroupInfo(rd) + if err != nil { + return err + } + } + + return nil +} + +func readXGroupInfo(rd *proto.Reader) (XInfoGroup, error) { + var group XInfoGroup + + n, err := rd.ReadArrayLen() + if err != nil { + return group, err + } + if n != 8 { + return group, fmt.Errorf("redis: got %d elements in XINFO GROUPS reply, wanted 8", n) + } + + for i := 0; i < 4; i++ { + key, err := rd.ReadString() + if err != nil { + return group, err + } + + val, err := rd.ReadString() + if err != nil { + return group, err + } + + switch key { + case "name": + group.Name = val + case "consumers": + group.Consumers, err = strconv.ParseInt(val, 0, 64) + if err != nil { + return group, err + } + case "pending": + group.Pending, err = strconv.ParseInt(val, 0, 64) + if err != nil { + return group, err + } + case "last-delivered-id": + group.LastDeliveredID = val + default: + return group, fmt.Errorf("redis: unexpected content %s in XINFO GROUPS reply", key) + } + } + + return group, nil +} + +//------------------------------------------------------------------------------ + +type XInfoStreamCmd struct { + baseCmd + val *XInfoStream +} + +type XInfoStream struct { + Length int64 + RadixTreeKeys int64 + RadixTreeNodes int64 + Groups int64 + LastGeneratedID string + FirstEntry XMessage + LastEntry XMessage +} + +var _ Cmder = (*XInfoStreamCmd)(nil) + +func NewXInfoStreamCmd(ctx context.Context, stream string) *XInfoStreamCmd { + return &XInfoStreamCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: []interface{}{"xinfo", "stream", stream}, + }, + } +} + +func (cmd *XInfoStreamCmd) SetVal(val *XInfoStream) { + cmd.val = val +} + +func (cmd *XInfoStreamCmd) Val() *XInfoStream { + return cmd.val +} + +func (cmd *XInfoStreamCmd) Result() (*XInfoStream, error) { + return cmd.val, cmd.err +} + +func (cmd *XInfoStreamCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XInfoStreamCmd) readReply(rd *proto.Reader) error { + v, err := rd.ReadReply(xStreamInfoParser) + if err != nil { + return err + } + cmd.val = v.(*XInfoStream) + return nil +} + +func xStreamInfoParser(rd *proto.Reader, n int64) (interface{}, error) { + if n != 14 { + return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM reply,"+ + "wanted 14", n) + } + var info XInfoStream + for i := 0; i < 7; i++ { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + switch key { + case "length": + info.Length, err = rd.ReadIntReply() + case "radix-tree-keys": + info.RadixTreeKeys, err = rd.ReadIntReply() + case "radix-tree-nodes": + info.RadixTreeNodes, err = rd.ReadIntReply() + case "groups": + info.Groups, err = rd.ReadIntReply() + case "last-generated-id": + info.LastGeneratedID, err = rd.ReadString() + case "first-entry": + info.FirstEntry, err = readXMessage(rd) + if err == Nil { + err = nil + } + case "last-entry": + info.LastEntry, err = readXMessage(rd) + if err == Nil { + err = nil + } + default: + return nil, fmt.Errorf("redis: unexpected content %s "+ + "in XINFO STREAM reply", key) + } + if err != nil { + return nil, err + } + } + return &info, nil +} + +//------------------------------------------------------------------------------ + +type XInfoStreamFullCmd struct { + baseCmd + val *XInfoStreamFull +} + +type XInfoStreamFull struct { + Length int64 + RadixTreeKeys int64 + RadixTreeNodes int64 + LastGeneratedID string + Entries []XMessage + Groups []XInfoStreamGroup +} + +type XInfoStreamGroup struct { + Name string + LastDeliveredID string + PelCount int64 + Pending []XInfoStreamGroupPending + Consumers []XInfoStreamConsumer +} + +type XInfoStreamGroupPending struct { + ID string + Consumer string + DeliveryTime time.Time + DeliveryCount int64 +} + +type XInfoStreamConsumer struct { + Name string + SeenTime time.Time + PelCount int64 + Pending []XInfoStreamConsumerPending +} + +type XInfoStreamConsumerPending struct { + ID string + DeliveryTime time.Time + DeliveryCount int64 +} + +var _ Cmder = (*XInfoStreamFullCmd)(nil) + +func NewXInfoStreamFullCmd(ctx context.Context, args ...interface{}) *XInfoStreamFullCmd { + return &XInfoStreamFullCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *XInfoStreamFullCmd) SetVal(val *XInfoStreamFull) { + cmd.val = val +} + +func (cmd *XInfoStreamFullCmd) Val() *XInfoStreamFull { + return cmd.val +} + +func (cmd *XInfoStreamFullCmd) Result() (*XInfoStreamFull, error) { + return cmd.val, cmd.err +} + +func (cmd *XInfoStreamFullCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *XInfoStreamFullCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + if n != 12 { + return fmt.Errorf("redis: got %d elements in XINFO STREAM FULL reply,"+ + "wanted 12", n) + } + + cmd.val = &XInfoStreamFull{} + + for i := 0; i < 6; i++ { + key, err := rd.ReadString() + if err != nil { + return err + } + + switch key { + case "length": + cmd.val.Length, err = rd.ReadIntReply() + case "radix-tree-keys": + cmd.val.RadixTreeKeys, err = rd.ReadIntReply() + case "radix-tree-nodes": + cmd.val.RadixTreeNodes, err = rd.ReadIntReply() + case "last-generated-id": + cmd.val.LastGeneratedID, err = rd.ReadString() + case "entries": + cmd.val.Entries, err = readXMessageSlice(rd) + case "groups": + cmd.val.Groups, err = readStreamGroups(rd) + default: + return fmt.Errorf("redis: unexpected content %s "+ + "in XINFO STREAM reply", key) + } + if err != nil { + return err + } + } + return nil +} + +func readStreamGroups(rd *proto.Reader) ([]XInfoStreamGroup, error) { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + groups := make([]XInfoStreamGroup, 0, n) + for i := 0; i < n; i++ { + nn, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if nn != 10 { + return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM FULL reply,"+ + "wanted 10", nn) + } + + group := XInfoStreamGroup{} + + for f := 0; f < 5; f++ { + key, err := rd.ReadString() + if err != nil { + return nil, err + } + + switch key { + case "name": + group.Name, err = rd.ReadString() + case "last-delivered-id": + group.LastDeliveredID, err = rd.ReadString() + case "pel-count": + group.PelCount, err = rd.ReadIntReply() + case "pending": + group.Pending, err = readXInfoStreamGroupPending(rd) + case "consumers": + group.Consumers, err = readXInfoStreamConsumers(rd) + default: + return nil, fmt.Errorf("redis: unexpected content %s "+ + "in XINFO STREAM reply", key) + } + + if err != nil { + return nil, err + } + } + + groups = append(groups, group) + } + + return groups, nil +} + +func readXInfoStreamGroupPending(rd *proto.Reader) ([]XInfoStreamGroupPending, error) { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + + pending := make([]XInfoStreamGroupPending, 0, n) + + for i := 0; i < n; i++ { + nn, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if nn != 4 { + return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM FULL reply,"+ + "wanted 4", nn) + } + + p := XInfoStreamGroupPending{} + + p.ID, err = rd.ReadString() + if err != nil { + return nil, err + } + + p.Consumer, err = rd.ReadString() + if err != nil { + return nil, err + } + + delivery, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + p.DeliveryTime = time.Unix(delivery/1000, delivery%1000*int64(time.Millisecond)) + + p.DeliveryCount, err = rd.ReadIntReply() + if err != nil { + return nil, err + } + + pending = append(pending, p) + } + + return pending, nil +} + +func readXInfoStreamConsumers(rd *proto.Reader) ([]XInfoStreamConsumer, error) { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + + consumers := make([]XInfoStreamConsumer, 0, n) + + for i := 0; i < n; i++ { + nn, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if nn != 8 { + return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM FULL reply,"+ + "wanted 8", nn) + } + + c := XInfoStreamConsumer{} + + for f := 0; f < 4; f++ { + cKey, err := rd.ReadString() + if err != nil { + return nil, err + } + + switch cKey { + case "name": + c.Name, err = rd.ReadString() + case "seen-time": + seen, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + c.SeenTime = time.Unix(seen/1000, seen%1000*int64(time.Millisecond)) + case "pel-count": + c.PelCount, err = rd.ReadIntReply() + case "pending": + pendingNumber, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + + c.Pending = make([]XInfoStreamConsumerPending, 0, pendingNumber) + + for pn := 0; pn < pendingNumber; pn++ { + nn, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if nn != 3 { + return nil, fmt.Errorf("redis: got %d elements in XINFO STREAM reply,"+ + "wanted 3", nn) + } + + p := XInfoStreamConsumerPending{} + + p.ID, err = rd.ReadString() + if err != nil { + return nil, err + } + + delivery, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + p.DeliveryTime = time.Unix(delivery/1000, delivery%1000*int64(time.Millisecond)) + + p.DeliveryCount, err = rd.ReadIntReply() + if err != nil { + return nil, err + } + + c.Pending = append(c.Pending, p) + } + default: + return nil, fmt.Errorf("redis: unexpected content %s "+ + "in XINFO STREAM reply", cKey) + } + if err != nil { + return nil, err + } + } + consumers = append(consumers, c) + } + + return consumers, nil +} + +//------------------------------------------------------------------------------ + +type ZSliceCmd struct { + baseCmd + + val []Z +} + +var _ Cmder = (*ZSliceCmd)(nil) + +func NewZSliceCmd(ctx context.Context, args ...interface{}) *ZSliceCmd { + return &ZSliceCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *ZSliceCmd) SetVal(val []Z) { + cmd.val = val +} + +func (cmd *ZSliceCmd) Val() []Z { + return cmd.val +} + +func (cmd *ZSliceCmd) Result() ([]Z, error) { + return cmd.val, cmd.err +} + +func (cmd *ZSliceCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *ZSliceCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]Z, n/2) + for i := 0; i < len(cmd.val); i++ { + member, err := rd.ReadString() + if err != nil { + return nil, err + } + + score, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } + + cmd.val[i] = Z{ + Member: member, + Score: score, + } + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type ZWithKeyCmd struct { + baseCmd + + val *ZWithKey +} + +var _ Cmder = (*ZWithKeyCmd)(nil) + +func NewZWithKeyCmd(ctx context.Context, args ...interface{}) *ZWithKeyCmd { + return &ZWithKeyCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *ZWithKeyCmd) SetVal(val *ZWithKey) { + cmd.val = val +} + +func (cmd *ZWithKeyCmd) Val() *ZWithKey { + return cmd.val +} + +func (cmd *ZWithKeyCmd) Result() (*ZWithKey, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *ZWithKeyCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *ZWithKeyCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + if n != 3 { + return nil, fmt.Errorf("got %d elements, expected 3", n) + } + + cmd.val = &ZWithKey{} + var err error + + cmd.val.Key, err = rd.ReadString() + if err != nil { + return nil, err + } + + cmd.val.Member, err = rd.ReadString() + if err != nil { + return nil, err + } + + cmd.val.Score, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type ScanCmd struct { + baseCmd + + page []string + cursor uint64 + + process cmdable +} + +var _ Cmder = (*ScanCmd)(nil) + +func NewScanCmd(ctx context.Context, process cmdable, args ...interface{}) *ScanCmd { + return &ScanCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + process: process, + } +} + +func (cmd *ScanCmd) SetVal(page []string, cursor uint64) { + cmd.page = page + cmd.cursor = cursor +} + +func (cmd *ScanCmd) Val() (keys []string, cursor uint64) { + return cmd.page, cmd.cursor +} + +func (cmd *ScanCmd) Result() (keys []string, cursor uint64, err error) { + return cmd.page, cmd.cursor, cmd.err +} + +func (cmd *ScanCmd) String() string { + return cmdString(cmd, cmd.page) +} + +func (cmd *ScanCmd) readReply(rd *proto.Reader) (err error) { + cmd.page, cmd.cursor, err = rd.ReadScanReply() + return err +} + +// Iterator creates a new ScanIterator. +func (cmd *ScanCmd) Iterator() *ScanIterator { + return &ScanIterator{ + cmd: cmd, + } +} + +//------------------------------------------------------------------------------ + +type ClusterNode struct { + ID string + Addr string +} + +type ClusterSlot struct { + Start int + End int + Nodes []ClusterNode +} + +type ClusterSlotsCmd struct { + baseCmd + + val []ClusterSlot +} + +var _ Cmder = (*ClusterSlotsCmd)(nil) + +func NewClusterSlotsCmd(ctx context.Context, args ...interface{}) *ClusterSlotsCmd { + return &ClusterSlotsCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *ClusterSlotsCmd) SetVal(val []ClusterSlot) { + cmd.val = val +} + +func (cmd *ClusterSlotsCmd) Val() []ClusterSlot { + return cmd.val +} + +func (cmd *ClusterSlotsCmd) Result() ([]ClusterSlot, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *ClusterSlotsCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *ClusterSlotsCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]ClusterSlot, n) + for i := 0; i < len(cmd.val); i++ { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if n < 2 { + err := fmt.Errorf("redis: got %d elements in cluster info, expected at least 2", n) + return nil, err + } + + start, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + end, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + nodes := make([]ClusterNode, n-2) + for j := 0; j < len(nodes); j++ { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if n != 2 && n != 3 { + err := fmt.Errorf("got %d elements in cluster info address, expected 2 or 3", n) + return nil, err + } + + ip, err := rd.ReadString() + if err != nil { + return nil, err + } + + port, err := rd.ReadString() + if err != nil { + return nil, err + } + + nodes[j].Addr = net.JoinHostPort(ip, port) + + if n == 3 { + id, err := rd.ReadString() + if err != nil { + return nil, err + } + nodes[j].ID = id + } + } + + cmd.val[i] = ClusterSlot{ + Start: int(start), + End: int(end), + Nodes: nodes, + } + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +// GeoLocation is used with GeoAdd to add geospatial location. +type GeoLocation struct { + Name string + Longitude, Latitude, Dist float64 + GeoHash int64 +} + +// GeoRadiusQuery is used with GeoRadius to query geospatial index. +type GeoRadiusQuery struct { + Radius float64 + // Can be m, km, ft, or mi. Default is km. + Unit string + WithCoord bool + WithDist bool + WithGeoHash bool + Count int + // Can be ASC or DESC. Default is no sort order. + Sort string + Store string + StoreDist string +} + +type GeoLocationCmd struct { + baseCmd + + q *GeoRadiusQuery + locations []GeoLocation +} + +var _ Cmder = (*GeoLocationCmd)(nil) + +func NewGeoLocationCmd(ctx context.Context, q *GeoRadiusQuery, args ...interface{}) *GeoLocationCmd { + return &GeoLocationCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: geoLocationArgs(q, args...), + }, + q: q, + } +} + +func geoLocationArgs(q *GeoRadiusQuery, args ...interface{}) []interface{} { + args = append(args, q.Radius) + if q.Unit != "" { + args = append(args, q.Unit) + } else { + args = append(args, "km") + } + if q.WithCoord { + args = append(args, "withcoord") + } + if q.WithDist { + args = append(args, "withdist") + } + if q.WithGeoHash { + args = append(args, "withhash") + } + if q.Count > 0 { + args = append(args, "count", q.Count) + } + if q.Sort != "" { + args = append(args, q.Sort) + } + if q.Store != "" { + args = append(args, "store") + args = append(args, q.Store) + } + if q.StoreDist != "" { + args = append(args, "storedist") + args = append(args, q.StoreDist) + } + return args +} + +func (cmd *GeoLocationCmd) SetVal(locations []GeoLocation) { + cmd.locations = locations +} + +func (cmd *GeoLocationCmd) Val() []GeoLocation { + return cmd.locations +} + +func (cmd *GeoLocationCmd) Result() ([]GeoLocation, error) { + return cmd.locations, cmd.err +} + +func (cmd *GeoLocationCmd) String() string { + return cmdString(cmd, cmd.locations) +} + +func (cmd *GeoLocationCmd) readReply(rd *proto.Reader) error { + v, err := rd.ReadArrayReply(newGeoLocationSliceParser(cmd.q)) + if err != nil { + return err + } + cmd.locations = v.([]GeoLocation) + return nil +} + +func newGeoLocationSliceParser(q *GeoRadiusQuery) proto.MultiBulkParse { + return func(rd *proto.Reader, n int64) (interface{}, error) { + locs := make([]GeoLocation, 0, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(newGeoLocationParser(q)) + if err != nil { + return nil, err + } + switch vv := v.(type) { + case string: + locs = append(locs, GeoLocation{ + Name: vv, + }) + case *GeoLocation: + // TODO: avoid copying + locs = append(locs, *vv) + default: + return nil, fmt.Errorf("got %T, expected string or *GeoLocation", v) + } + } + return locs, nil + } +} + +func newGeoLocationParser(q *GeoRadiusQuery) proto.MultiBulkParse { + return func(rd *proto.Reader, n int64) (interface{}, error) { + var loc GeoLocation + var err error + + loc.Name, err = rd.ReadString() + if err != nil { + return nil, err + } + if q.WithDist { + loc.Dist, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + } + if q.WithGeoHash { + loc.GeoHash, err = rd.ReadIntReply() + if err != nil { + return nil, err + } + } + if q.WithCoord { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if n != 2 { + return nil, fmt.Errorf("got %d coordinates, expected 2", n) + } + + loc.Longitude, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + loc.Latitude, err = rd.ReadFloatReply() + if err != nil { + return nil, err + } + } + + return &loc, nil + } +} + +//------------------------------------------------------------------------------ + +// GeoSearchQuery is used for GEOSearch/GEOSearchStore command query. +type GeoSearchQuery struct { + Member string + + // Latitude and Longitude when using FromLonLat option. + Longitude float64 + Latitude float64 + + // Distance and unit when using ByRadius option. + // Can use m, km, ft, or mi. Default is km. + Radius float64 + RadiusUnit string + + // Height, width and unit when using ByBox option. + // Can be m, km, ft, or mi. Default is km. + BoxWidth float64 + BoxHeight float64 + BoxUnit string + + // Can be ASC or DESC. Default is no sort order. + Sort string + Count int + CountAny bool +} + +type GeoSearchLocationQuery struct { + GeoSearchQuery + + WithCoord bool + WithDist bool + WithHash bool +} + +type GeoSearchStoreQuery struct { + GeoSearchQuery + + // When using the StoreDist option, the command stores the items in a + // sorted set populated with their distance from the center of the circle or box, + // as a floating-point number, in the same unit specified for that shape. + StoreDist bool +} + +func geoSearchLocationArgs(q *GeoSearchLocationQuery, args []interface{}) []interface{} { + args = geoSearchArgs(&q.GeoSearchQuery, args) + + if q.WithCoord { + args = append(args, "withcoord") + } + if q.WithDist { + args = append(args, "withdist") + } + if q.WithHash { + args = append(args, "withhash") + } + + return args +} + +func geoSearchArgs(q *GeoSearchQuery, args []interface{}) []interface{} { + if q.Member != "" { + args = append(args, "frommember", q.Member) + } else { + args = append(args, "fromlonlat", q.Longitude, q.Latitude) + } + + if q.Radius > 0 { + if q.RadiusUnit == "" { + q.RadiusUnit = "km" + } + args = append(args, "byradius", q.Radius, q.RadiusUnit) + } else { + if q.BoxUnit == "" { + q.BoxUnit = "km" + } + args = append(args, "bybox", q.BoxWidth, q.BoxHeight, q.BoxUnit) + } + + if q.Sort != "" { + args = append(args, q.Sort) + } + + if q.Count > 0 { + args = append(args, "count", q.Count) + if q.CountAny { + args = append(args, "any") + } + } + + return args +} + +type GeoSearchLocationCmd struct { + baseCmd + + opt *GeoSearchLocationQuery + val []GeoLocation +} + +var _ Cmder = (*GeoSearchLocationCmd)(nil) + +func NewGeoSearchLocationCmd( + ctx context.Context, opt *GeoSearchLocationQuery, args ...interface{}, +) *GeoSearchLocationCmd { + return &GeoSearchLocationCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + opt: opt, + } +} + +func (cmd *GeoSearchLocationCmd) SetVal(val []GeoLocation) { + cmd.val = val +} + +func (cmd *GeoSearchLocationCmd) Val() []GeoLocation { + return cmd.val +} + +func (cmd *GeoSearchLocationCmd) Result() ([]GeoLocation, error) { + return cmd.val, cmd.err +} + +func (cmd *GeoSearchLocationCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *GeoSearchLocationCmd) readReply(rd *proto.Reader) error { + n, err := rd.ReadArrayLen() + if err != nil { + return err + } + + cmd.val = make([]GeoLocation, n) + for i := 0; i < n; i++ { + _, err = rd.ReadArrayLen() + if err != nil { + return err + } + + var loc GeoLocation + + loc.Name, err = rd.ReadString() + if err != nil { + return err + } + if cmd.opt.WithDist { + loc.Dist, err = rd.ReadFloatReply() + if err != nil { + return err + } + } + if cmd.opt.WithHash { + loc.GeoHash, err = rd.ReadIntReply() + if err != nil { + return err + } + } + if cmd.opt.WithCoord { + nn, err := rd.ReadArrayLen() + if err != nil { + return err + } + if nn != 2 { + return fmt.Errorf("got %d coordinates, expected 2", nn) + } + + loc.Longitude, err = rd.ReadFloatReply() + if err != nil { + return err + } + loc.Latitude, err = rd.ReadFloatReply() + if err != nil { + return err + } + } + + cmd.val[i] = loc + } + + return nil +} + +//------------------------------------------------------------------------------ + +type GeoPos struct { + Longitude, Latitude float64 +} + +type GeoPosCmd struct { + baseCmd + + val []*GeoPos +} + +var _ Cmder = (*GeoPosCmd)(nil) + +func NewGeoPosCmd(ctx context.Context, args ...interface{}) *GeoPosCmd { + return &GeoPosCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *GeoPosCmd) SetVal(val []*GeoPos) { + cmd.val = val +} + +func (cmd *GeoPosCmd) Val() []*GeoPos { + return cmd.val +} + +func (cmd *GeoPosCmd) Result() ([]*GeoPos, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *GeoPosCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *GeoPosCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]*GeoPos, n) + for i := 0; i < len(cmd.val); i++ { + i := i + _, err := rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { + longitude, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } + + latitude, err := rd.ReadFloatReply() + if err != nil { + return nil, err + } + + cmd.val[i] = &GeoPos{ + Longitude: longitude, + Latitude: latitude, + } + return nil, nil + }) + if err != nil { + if err == Nil { + cmd.val[i] = nil + continue + } + return nil, err + } + } + return nil, nil + }) + return err +} + +//------------------------------------------------------------------------------ + +type CommandInfo struct { + Name string + Arity int8 + Flags []string + ACLFlags []string + FirstKeyPos int8 + LastKeyPos int8 + StepCount int8 + ReadOnly bool +} + +type CommandsInfoCmd struct { + baseCmd + + val map[string]*CommandInfo +} + +var _ Cmder = (*CommandsInfoCmd)(nil) + +func NewCommandsInfoCmd(ctx context.Context, args ...interface{}) *CommandsInfoCmd { + return &CommandsInfoCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *CommandsInfoCmd) SetVal(val map[string]*CommandInfo) { + cmd.val = val +} + +func (cmd *CommandsInfoCmd) Val() map[string]*CommandInfo { + return cmd.val +} + +func (cmd *CommandsInfoCmd) Result() (map[string]*CommandInfo, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *CommandsInfoCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *CommandsInfoCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make(map[string]*CommandInfo, n) + for i := int64(0); i < n; i++ { + v, err := rd.ReadReply(commandInfoParser) + if err != nil { + return nil, err + } + vv := v.(*CommandInfo) + cmd.val[vv.Name] = vv + } + return nil, nil + }) + return err +} + +func commandInfoParser(rd *proto.Reader, n int64) (interface{}, error) { + const numArgRedis5 = 6 + const numArgRedis6 = 7 + + switch n { + case numArgRedis5, numArgRedis6: + // continue + default: + return nil, fmt.Errorf("redis: got %d elements in COMMAND reply, wanted 7", n) + } + + var cmd CommandInfo + var err error + + cmd.Name, err = rd.ReadString() + if err != nil { + return nil, err + } + + arity, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.Arity = int8(arity) + + _, err = rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.Flags = make([]string, n) + for i := 0; i < len(cmd.Flags); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.Flags[i] = "" + case err != nil: + return nil, err + default: + cmd.Flags[i] = s + } + } + return nil, nil + }) + if err != nil { + return nil, err + } + + firstKeyPos, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.FirstKeyPos = int8(firstKeyPos) + + lastKeyPos, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.LastKeyPos = int8(lastKeyPos) + + stepCount, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + cmd.StepCount = int8(stepCount) + + for _, flag := range cmd.Flags { + if flag == "readonly" { + cmd.ReadOnly = true + break + } + } + + if n == numArgRedis5 { + return &cmd, nil + } + + _, err = rd.ReadReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.ACLFlags = make([]string, n) + for i := 0; i < len(cmd.ACLFlags); i++ { + switch s, err := rd.ReadString(); { + case err == Nil: + cmd.ACLFlags[i] = "" + case err != nil: + return nil, err + default: + cmd.ACLFlags[i] = s + } + } + return nil, nil + }) + if err != nil { + return nil, err + } + + return &cmd, nil +} + +//------------------------------------------------------------------------------ + +type cmdsInfoCache struct { + fn func(ctx context.Context) (map[string]*CommandInfo, error) + + once internal.Once + cmds map[string]*CommandInfo +} + +func newCmdsInfoCache(fn func(ctx context.Context) (map[string]*CommandInfo, error)) *cmdsInfoCache { + return &cmdsInfoCache{ + fn: fn, + } +} + +func (c *cmdsInfoCache) Get(ctx context.Context) (map[string]*CommandInfo, error) { + err := c.once.Do(func() error { + cmds, err := c.fn(ctx) + if err != nil { + return err + } + + // Extensions have cmd names in upper case. Convert them to lower case. + for k, v := range cmds { + lower := internal.ToLower(k) + if lower != k { + cmds[lower] = v + } + } + + c.cmds = cmds + return nil + }) + return c.cmds, err +} + +//------------------------------------------------------------------------------ + +type SlowLog struct { + ID int64 + Time time.Time + Duration time.Duration + Args []string + // These are also optional fields emitted only by Redis 4.0 or greater: + // https://redis.io/commands/slowlog#output-format + ClientAddr string + ClientName string +} + +type SlowLogCmd struct { + baseCmd + + val []SlowLog +} + +var _ Cmder = (*SlowLogCmd)(nil) + +func NewSlowLogCmd(ctx context.Context, args ...interface{}) *SlowLogCmd { + return &SlowLogCmd{ + baseCmd: baseCmd{ + ctx: ctx, + args: args, + }, + } +} + +func (cmd *SlowLogCmd) SetVal(val []SlowLog) { + cmd.val = val +} + +func (cmd *SlowLogCmd) Val() []SlowLog { + return cmd.val +} + +func (cmd *SlowLogCmd) Result() ([]SlowLog, error) { + return cmd.Val(), cmd.Err() +} + +func (cmd *SlowLogCmd) String() string { + return cmdString(cmd, cmd.val) +} + +func (cmd *SlowLogCmd) readReply(rd *proto.Reader) error { + _, err := rd.ReadArrayReply(func(rd *proto.Reader, n int64) (interface{}, error) { + cmd.val = make([]SlowLog, n) + for i := 0; i < len(cmd.val); i++ { + n, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if n < 4 { + err := fmt.Errorf("redis: got %d elements in slowlog get, expected at least 4", n) + return nil, err + } + + id, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + + createdAt, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + createdAtTime := time.Unix(createdAt, 0) + + costs, err := rd.ReadIntReply() + if err != nil { + return nil, err + } + costsDuration := time.Duration(costs) * time.Microsecond + + cmdLen, err := rd.ReadArrayLen() + if err != nil { + return nil, err + } + if cmdLen < 1 { + err := fmt.Errorf("redis: got %d elements commands reply in slowlog get, expected at least 1", cmdLen) + return nil, err + } + + cmdString := make([]string, cmdLen) + for i := 0; i < cmdLen; i++ { + cmdString[i], err = rd.ReadString() + if err != nil { + return nil, err + } + } + + var address, name string + for i := 4; i < n; i++ { + str, err := rd.ReadString() + if err != nil { + return nil, err + } + if i == 4 { + address = str + } else if i == 5 { + name = str + } + } + + cmd.val[i] = SlowLog{ + ID: id, + Time: createdAtTime, + Duration: costsDuration, + Args: cmdString, + ClientAddr: address, + ClientName: name, + } + } + return nil, nil + }) + return err +} diff --git a/vendor/github.com/go-redis/redis/v8/commands.go b/vendor/github.com/go-redis/redis/v8/commands.go new file mode 100644 index 0000000000..bbfe089df1 --- /dev/null +++ b/vendor/github.com/go-redis/redis/v8/commands.go @@ -0,0 +1,3475 @@ +package redis + +import ( + "context" + "errors" + "io" + "time" + + "github.com/go-redis/redis/v8/internal" +) + +// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, +// otherwise you will receive an error: (error) ERR syntax error. +// For example: +// +// rdb.Set(ctx, key, value, redis.KeepTTL) +const KeepTTL = -1 + +func usePrecise(dur time.Duration) bool { + return dur < time.Second || dur%time.Second != 0 +} + +func formatMs(ctx context.Context, dur time.Duration) int64 { + if dur > 0 && dur < time.Millisecond { + internal.Logger.Printf( + ctx, + "specified duration is %s, but minimal supported value is %s - truncating to 1ms", + dur, time.Millisecond, + ) + return 1 + } + return int64(dur / time.Millisecond) +} + +func formatSec(ctx context.Context, dur time.Duration) int64 { + if dur > 0 && dur < time.Second { + internal.Logger.Printf( + ctx, + "specified duration is %s, but minimal supported value is %s - truncating to 1s", + dur, time.Second, + ) + return 1 + } + return int64(dur / time.Second) +} + +func appendArgs(dst, src []interface{}) []interface{} { + if len(src) == 1 { + return appendArg(dst, src[0]) + } + + dst = append(dst, src...) + return dst +} + +func appendArg(dst []interface{}, arg interface{}) []interface{} { + switch arg := arg.(type) { + case []string: + for _, s := range arg { + dst = append(dst, s) + } + return dst + case []interface{}: + dst = append(dst, arg...) + return dst + case map[string]interface{}: + for k, v := range arg { + dst = append(dst, k, v) + } + return dst + case map[string]string: + for k, v := range arg { + dst = append(dst, k, v) + } + return dst + default: + return append(dst, arg) + } +} + +type Cmdable interface { + Pipeline() Pipeliner + Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) + + TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error) + TxPipeline() Pipeliner + + Command(ctx context.Context) *CommandsInfoCmd + ClientGetName(ctx context.Context) *StringCmd + Echo(ctx context.Context, message interface{}) *StringCmd + Ping(ctx context.Context) *StatusCmd + Quit(ctx context.Context) *StatusCmd + Del(ctx context.Context, keys ...string) *IntCmd + Unlink(ctx context.Context, keys ...string) *IntCmd + Dump(ctx context.Context, key string) *StringCmd + Exists(ctx context.Context, keys ...string) *IntCmd + Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd + ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd + ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd + ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd + ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd + ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd + Keys(ctx context.Context, pattern string) *StringSliceCmd + Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd + Move(ctx context.Context, key string, db int) *BoolCmd + ObjectRefCount(ctx context.Context, key string) *IntCmd + ObjectEncoding(ctx context.Context, key string) *StringCmd + ObjectIdleTime(ctx context.Context, key string) *DurationCmd + Persist(ctx context.Context, key string) *BoolCmd + PExpire(ctx context.Context, key string, expiration time.Duration) *BoolCmd + PExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd + PTTL(ctx context.Context, key string) *DurationCmd + RandomKey(ctx context.Context) *StringCmd + Rename(ctx context.Context, key, newkey string) *StatusCmd + RenameNX(ctx context.Context, key, newkey string) *BoolCmd + Restore(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd + RestoreReplace(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd + Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd + SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd + SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd + Touch(ctx context.Context, keys ...string) *IntCmd + TTL(ctx context.Context, key string) *DurationCmd + Type(ctx context.Context, key string) *StatusCmd + Append(ctx context.Context, key, value string) *IntCmd + Decr(ctx context.Context, key string) *IntCmd + DecrBy(ctx context.Context, key string, decrement int64) *IntCmd + Get(ctx context.Context, key string) *StringCmd + GetRange(ctx context.Context, key string, start, end int64) *StringCmd + GetSet(ctx context.Context, key string, value interface{}) *StringCmd + GetEx(ctx context.Context, key string, expiration time.Duration) *StringCmd + GetDel(ctx context.Context, key string) *StringCmd + Incr(ctx context.Context, key string) *IntCmd + IncrBy(ctx context.Context, key string, value int64) *IntCmd + IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd + MGet(ctx context.Context, keys ...string) *SliceCmd + MSet(ctx context.Context, values ...interface{}) *StatusCmd + MSetNX(ctx context.Context, values ...interface{}) *BoolCmd + Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd + SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd + // TODO: rename to SetEx + SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd + SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd + SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd + SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd + StrLen(ctx context.Context, key string) *IntCmd + Copy(ctx context.Context, sourceKey string, destKey string, db int, replace bool) *IntCmd + + GetBit(ctx context.Context, key string, offset int64) *IntCmd + SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd + BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd + BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd + BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd + BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd + BitOpNot(ctx context.Context, destKey string, key string) *IntCmd + BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd + BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd + + Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd + ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd + SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd + HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd + ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd + + HDel(ctx context.Context, key string, fields ...string) *IntCmd + HExists(ctx context.Context, key, field string) *BoolCmd + HGet(ctx context.Context, key, field string) *StringCmd + HGetAll(ctx context.Context, key string) *StringStringMapCmd + HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd + HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd + HKeys(ctx context.Context, key string) *StringSliceCmd + HLen(ctx context.Context, key string) *IntCmd + HMGet(ctx context.Context, key string, fields ...string) *SliceCmd + HSet(ctx context.Context, key string, values ...interface{}) *IntCmd + HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd + HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd + HVals(ctx context.Context, key string) *StringSliceCmd + HRandField(ctx context.Context, key string, count int, withValues bool) *StringSliceCmd + + BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd + BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd + BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd + LIndex(ctx context.Context, key string, index int64) *StringCmd + LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd + LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd + LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd + LLen(ctx context.Context, key string) *IntCmd + LPop(ctx context.Context, key string) *StringCmd + LPopCount(ctx context.Context, key string, count int) *StringSliceCmd + LPos(ctx context.Context, key string, value string, args LPosArgs) *IntCmd + LPosCount(ctx context.Context, key string, value string, count int64, args LPosArgs) *IntSliceCmd + LPush(ctx context.Context, key string, values ...interface{}) *IntCmd + LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd + LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd + LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd + LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd + LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd + RPop(ctx context.Context, key string) *StringCmd + RPopCount(ctx context.Context, key string, count int) *StringSliceCmd + RPopLPush(ctx context.Context, source, destination string) *StringCmd + RPush(ctx context.Context, key string, values ...interface{}) *IntCmd + RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd + LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd + BLMove(ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration) *StringCmd + + SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd + SCard(ctx context.Context, key string) *IntCmd + SDiff(ctx context.Context, keys ...string) *StringSliceCmd + SDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd + SInter(ctx context.Context, keys ...string) *StringSliceCmd + SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd + SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd + SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd + SMembers(ctx context.Context, key string) *StringSliceCmd + SMembersMap(ctx context.Context, key string) *StringStructMapCmd + SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd + SPop(ctx context.Context, key string) *StringCmd + SPopN(ctx context.Context, key string, count int64) *StringSliceCmd + SRandMember(ctx context.Context, key string) *StringCmd + SRandMemberN(ctx context.Context, key string, count int64) *StringSliceCmd + SRem(ctx context.Context, key string, members ...interface{}) *IntCmd + SUnion(ctx context.Context, keys ...string) *StringSliceCmd + SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd + + XAdd(ctx context.Context, a *XAddArgs) *StringCmd + XDel(ctx context.Context, stream string, ids ...string) *IntCmd + XLen(ctx context.Context, stream string) *IntCmd + XRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd + XRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd + XRevRange(ctx context.Context, stream string, start, stop string) *XMessageSliceCmd + XRevRangeN(ctx context.Context, stream string, start, stop string, count int64) *XMessageSliceCmd + XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd + XReadStreams(ctx context.Context, streams ...string) *XStreamSliceCmd + XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd + XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd + XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd + XGroupDestroy(ctx context.Context, stream, group string) *IntCmd + XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd + XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd + XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd + XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd + XPending(ctx context.Context, stream, group string) *XPendingCmd + XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd + XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd + XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd + XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd + XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd + + // TODO: XTrim and XTrimApprox remove in v9. + XTrim(ctx context.Context, key string, maxLen int64) *IntCmd + XTrimApprox(ctx context.Context, key string, maxLen int64) *IntCmd + XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd + XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd + XTrimMinID(ctx context.Context, key string, minID string) *IntCmd + XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd + XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd + XInfoStream(ctx context.Context, key string) *XInfoStreamCmd + XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd + XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd + + BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd + BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd + + // TODO: remove + // ZAddCh + // ZIncr + // ZAddNXCh + // ZAddXXCh + // ZIncrNX + // ZIncrXX + // in v9. + // use ZAddArgs and ZAddArgsIncr. + + ZAdd(ctx context.Context, key string, members ...*Z) *IntCmd + ZAddNX(ctx context.Context, key string, members ...*Z) *IntCmd + ZAddXX(ctx context.Context, key string, members ...*Z) *IntCmd + ZAddCh(ctx context.Context, key string, members ...*Z) *IntCmd + ZAddNXCh(ctx context.Context, key string, members ...*Z) *IntCmd + ZAddXXCh(ctx context.Context, key string, members ...*Z) *IntCmd + ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd + ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd + ZIncr(ctx context.Context, key string, member *Z) *FloatCmd + ZIncrNX(ctx context.Context, key string, member *Z) *FloatCmd + ZIncrXX(ctx context.Context, key string, member *Z) *FloatCmd + ZCard(ctx context.Context, key string) *IntCmd + ZCount(ctx context.Context, key, min, max string) *IntCmd + ZLexCount(ctx context.Context, key, min, max string) *IntCmd + ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd + ZInter(ctx context.Context, store *ZStore) *StringSliceCmd + ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd + ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd + ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd + ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd + ZPopMin(ctx context.Context, key string, count ...int64) *ZSliceCmd + ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd + ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd + ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd + ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd + ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd + ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd + ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd + ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd + ZRank(ctx context.Context, key, member string) *IntCmd + ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd + ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd + ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd + ZRemRangeByLex(ctx context.Context, key, min, max string) *IntCmd + ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd + ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd + ZRevRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd + ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd + ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd + ZRevRank(ctx context.Context, key, member string) *IntCmd + ZScore(ctx context.Context, key, member string) *FloatCmd + ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd + ZUnion(ctx context.Context, store ZStore) *StringSliceCmd + ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd + ZRandMember(ctx context.Context, key string, count int, withScores bool) *StringSliceCmd + ZDiff(ctx context.Context, keys ...string) *StringSliceCmd + ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd + ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd + + PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd + PFCount(ctx context.Context, keys ...string) *IntCmd + PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd + + BgRewriteAOF(ctx context.Context) *StatusCmd + BgSave(ctx context.Context) *StatusCmd + ClientKill(ctx context.Context, ipPort string) *StatusCmd + ClientKillByFilter(ctx context.Context, keys ...string) *IntCmd + ClientList(ctx context.Context) *StringCmd + ClientPause(ctx context.Context, dur time.Duration) *BoolCmd + ClientID(ctx context.Context) *IntCmd + ConfigGet(ctx context.Context, parameter string) *SliceCmd + ConfigResetStat(ctx context.Context) *StatusCmd + ConfigSet(ctx context.Context, parameter, value string) *StatusCmd + ConfigRewrite(ctx context.Context) *StatusCmd + DBSize(ctx context.Context) *IntCmd + FlushAll(ctx context.Context) *StatusCmd + FlushAllAsync(ctx context.Context) *StatusCmd + FlushDB(ctx context.Context) *StatusCmd + FlushDBAsync(ctx context.Context) *StatusCmd + Info(ctx context.Context, section ...string) *StringCmd + LastSave(ctx context.Context) *IntCmd + Save(ctx context.Context) *StatusCmd + Shutdown(ctx context.Context) *StatusCmd + ShutdownSave(ctx context.Context) *StatusCmd + ShutdownNoSave(ctx context.Context) *StatusCmd + SlaveOf(ctx context.Context, host, port string) *StatusCmd + Time(ctx context.Context) *TimeCmd + DebugObject(ctx context.Context, key string) *StringCmd + ReadOnly(ctx context.Context) *StatusCmd + ReadWrite(ctx context.Context) *StatusCmd + MemoryUsage(ctx context.Context, key string, samples ...int) *IntCmd + + Eval(ctx context.Context, script string, keys []string, args ...interface{}) *Cmd + EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *Cmd + ScriptExists(ctx context.Context, hashes ...string) *BoolSliceCmd + ScriptFlush(ctx context.Context) *StatusCmd + ScriptKill(ctx context.Context) *StatusCmd + ScriptLoad(ctx context.Context, script string) *StringCmd + + Publish(ctx context.Context, channel string, message interface{}) *IntCmd + PubSubChannels(ctx context.Context, pattern string) *StringSliceCmd + PubSubNumSub(ctx context.Context, channels ...string) *StringIntMapCmd + PubSubNumPat(ctx context.Context) *IntCmd + + ClusterSlots(ctx context.Context) *ClusterSlotsCmd + ClusterNodes(ctx context.Context) *StringCmd + ClusterMeet(ctx context.Context, host, port string) *StatusCmd + ClusterForget(ctx context.Context, nodeID string) *StatusCmd + ClusterReplicate(ctx context.Context, nodeID string) *StatusCmd + ClusterResetSoft(ctx context.Context) *StatusCmd + ClusterResetHard(ctx context.Context) *StatusCmd + ClusterInfo(ctx context.Context) *StringCmd + ClusterKeySlot(ctx context.Context, key string) *IntCmd + ClusterGetKeysInSlot(ctx context.Context, slot int, count int) *StringSliceCmd + ClusterCountFailureReports(ctx context.Context, nodeID string) *IntCmd + ClusterCountKeysInSlot(ctx context.Context, slot int) *IntCmd + ClusterDelSlots(ctx context.Context, slots ...int) *StatusCmd + ClusterDelSlotsRange(ctx context.Context, min, max int) *StatusCmd + ClusterSaveConfig(ctx context.Context) *StatusCmd + ClusterSlaves(ctx context.Context, nodeID string) *StringSliceCmd + ClusterFailover(ctx context.Context) *StatusCmd + ClusterAddSlots(ctx context.Context, slots ...int) *StatusCmd + ClusterAddSlotsRange(ctx context.Context, min, max int) *StatusCmd + + GeoAdd(ctx context.Context, key string, geoLocation ...*GeoLocation) *IntCmd + GeoPos(ctx context.Context, key string, members ...string) *GeoPosCmd + GeoRadius(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *GeoLocationCmd + GeoRadiusStore(ctx context.Context, key string, longitude, latitude float64, query *GeoRadiusQuery) *IntCmd + GeoRadiusByMember(ctx context.Context, key, member string, query *GeoRadiusQuery) *GeoLocationCmd + GeoRadiusByMemberStore(ctx context.Context, key, member string, query *GeoRadiusQuery) *IntCmd + GeoSearch(ctx context.Context, key string, q *GeoSearchQuery) *StringSliceCmd + GeoSearchLocation(ctx context.Context, key string, q *GeoSearchLocationQuery) *GeoSearchLocationCmd + GeoSearchStore(ctx context.Context, key, store string, q *GeoSearchStoreQuery) *IntCmd + GeoDist(ctx context.Context, key string, member1, member2, unit string) *FloatCmd + GeoHash(ctx context.Context, key string, members ...string) *StringSliceCmd +} + +type StatefulCmdable interface { + Cmdable + Auth(ctx context.Context, password string) *StatusCmd + AuthACL(ctx context.Context, username, password string) *StatusCmd + Select(ctx context.Context, index int) *StatusCmd + SwapDB(ctx context.Context, index1, index2 int) *StatusCmd + ClientSetName(ctx context.Context, name string) *BoolCmd +} + +var ( + _ Cmdable = (*Client)(nil) + _ Cmdable = (*Tx)(nil) + _ Cmdable = (*Ring)(nil) + _ Cmdable = (*ClusterClient)(nil) +) + +type cmdable func(ctx context.Context, cmd Cmder) error + +type statefulCmdable func(ctx context.Context, cmd Cmder) error + +//------------------------------------------------------------------------------ + +func (c statefulCmdable) Auth(ctx context.Context, password string) *StatusCmd { + cmd := NewStatusCmd(ctx, "auth", password) + _ = c(ctx, cmd) + return cmd +} + +// AuthACL Perform an AUTH command, using the given user and pass. +// Should be used to authenticate the current connection with one of the connections defined in the ACL list +// when connecting to a Redis 6.0 instance, or greater, that is using the Redis ACL system. +func (c statefulCmdable) AuthACL(ctx context.Context, username, password string) *StatusCmd { + cmd := NewStatusCmd(ctx, "auth", username, password) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Wait(ctx context.Context, numSlaves int, timeout time.Duration) *IntCmd { + cmd := NewIntCmd(ctx, "wait", numSlaves, int(timeout/time.Millisecond)) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + +func (c statefulCmdable) Select(ctx context.Context, index int) *StatusCmd { + cmd := NewStatusCmd(ctx, "select", index) + _ = c(ctx, cmd) + return cmd +} + +func (c statefulCmdable) SwapDB(ctx context.Context, index1, index2 int) *StatusCmd { + cmd := NewStatusCmd(ctx, "swapdb", index1, index2) + _ = c(ctx, cmd) + return cmd +} + +// ClientSetName assigns a name to the connection. +func (c statefulCmdable) ClientSetName(ctx context.Context, name string) *BoolCmd { + cmd := NewBoolCmd(ctx, "client", "setname", name) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c cmdable) Command(ctx context.Context) *CommandsInfoCmd { + cmd := NewCommandsInfoCmd(ctx, "command") + _ = c(ctx, cmd) + return cmd +} + +// ClientGetName returns the name of the connection. +func (c cmdable) ClientGetName(ctx context.Context) *StringCmd { + cmd := NewStringCmd(ctx, "client", "getname") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Echo(ctx context.Context, message interface{}) *StringCmd { + cmd := NewStringCmd(ctx, "echo", message) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Ping(ctx context.Context) *StatusCmd { + cmd := NewStatusCmd(ctx, "ping") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Quit(_ context.Context) *StatusCmd { + panic("not implemented") +} + +func (c cmdable) Del(ctx context.Context, keys ...string) *IntCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "del" + for i, key := range keys { + args[1+i] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Unlink(ctx context.Context, keys ...string) *IntCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "unlink" + for i, key := range keys { + args[1+i] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Dump(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "dump", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Exists(ctx context.Context, keys ...string) *IntCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "exists" + for i, key := range keys { + args[1+i] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Expire(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "") +} + +func (c cmdable) ExpireNX(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "NX") +} + +func (c cmdable) ExpireXX(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "XX") +} + +func (c cmdable) ExpireGT(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "GT") +} + +func (c cmdable) ExpireLT(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + return c.expire(ctx, key, expiration, "LT") +} + +func (c cmdable) expire( + ctx context.Context, key string, expiration time.Duration, mode string, +) *BoolCmd { + args := make([]interface{}, 3, 4) + args[0] = "expire" + args[1] = key + args[2] = formatSec(ctx, expiration) + if mode != "" { + args = append(args, mode) + } + + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd { + cmd := NewBoolCmd(ctx, "expireat", key, tm.Unix()) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Keys(ctx context.Context, pattern string) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "keys", pattern) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Migrate(ctx context.Context, host, port, key string, db int, timeout time.Duration) *StatusCmd { + cmd := NewStatusCmd( + ctx, + "migrate", + host, + port, + key, + db, + formatMs(ctx, timeout), + ) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Move(ctx context.Context, key string, db int) *BoolCmd { + cmd := NewBoolCmd(ctx, "move", key, db) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ObjectRefCount(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "object", "refcount", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ObjectEncoding(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "object", "encoding", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ObjectIdleTime(ctx context.Context, key string) *DurationCmd { + cmd := NewDurationCmd(ctx, time.Second, "object", "idletime", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Persist(ctx context.Context, key string) *BoolCmd { + cmd := NewBoolCmd(ctx, "persist", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) PExpire(ctx context.Context, key string, expiration time.Duration) *BoolCmd { + cmd := NewBoolCmd(ctx, "pexpire", key, formatMs(ctx, expiration)) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) PExpireAt(ctx context.Context, key string, tm time.Time) *BoolCmd { + cmd := NewBoolCmd( + ctx, + "pexpireat", + key, + tm.UnixNano()/int64(time.Millisecond), + ) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) PTTL(ctx context.Context, key string) *DurationCmd { + cmd := NewDurationCmd(ctx, time.Millisecond, "pttl", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) RandomKey(ctx context.Context) *StringCmd { + cmd := NewStringCmd(ctx, "randomkey") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Rename(ctx context.Context, key, newkey string) *StatusCmd { + cmd := NewStatusCmd(ctx, "rename", key, newkey) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) RenameNX(ctx context.Context, key, newkey string) *BoolCmd { + cmd := NewBoolCmd(ctx, "renamenx", key, newkey) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Restore(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd { + cmd := NewStatusCmd( + ctx, + "restore", + key, + formatMs(ctx, ttl), + value, + ) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) RestoreReplace(ctx context.Context, key string, ttl time.Duration, value string) *StatusCmd { + cmd := NewStatusCmd( + ctx, + "restore", + key, + formatMs(ctx, ttl), + value, + "replace", + ) + _ = c(ctx, cmd) + return cmd +} + +type Sort struct { + By string + Offset, Count int64 + Get []string + Order string + Alpha bool +} + +func (sort *Sort) args(key string) []interface{} { + args := []interface{}{"sort", key} + if sort.By != "" { + args = append(args, "by", sort.By) + } + if sort.Offset != 0 || sort.Count != 0 { + args = append(args, "limit", sort.Offset, sort.Count) + } + for _, get := range sort.Get { + args = append(args, "get", get) + } + if sort.Order != "" { + args = append(args, sort.Order) + } + if sort.Alpha { + args = append(args, "alpha") + } + return args +} + +func (c cmdable) Sort(ctx context.Context, key string, sort *Sort) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, sort.args(key)...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SortStore(ctx context.Context, key, store string, sort *Sort) *IntCmd { + args := sort.args(key) + if store != "" { + args = append(args, "store", store) + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SortInterfaces(ctx context.Context, key string, sort *Sort) *SliceCmd { + cmd := NewSliceCmd(ctx, sort.args(key)...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Touch(ctx context.Context, keys ...string) *IntCmd { + args := make([]interface{}, len(keys)+1) + args[0] = "touch" + for i, key := range keys { + args[i+1] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) TTL(ctx context.Context, key string) *DurationCmd { + cmd := NewDurationCmd(ctx, time.Second, "ttl", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Type(ctx context.Context, key string) *StatusCmd { + cmd := NewStatusCmd(ctx, "type", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Append(ctx context.Context, key, value string) *IntCmd { + cmd := NewIntCmd(ctx, "append", key, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Decr(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "decr", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) DecrBy(ctx context.Context, key string, decrement int64) *IntCmd { + cmd := NewIntCmd(ctx, "decrby", key, decrement) + _ = c(ctx, cmd) + return cmd +} + +// Get Redis `GET key` command. It returns redis.Nil error when key does not exist. +func (c cmdable) Get(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "get", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) GetRange(ctx context.Context, key string, start, end int64) *StringCmd { + cmd := NewStringCmd(ctx, "getrange", key, start, end) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) GetSet(ctx context.Context, key string, value interface{}) *StringCmd { + cmd := NewStringCmd(ctx, "getset", key, value) + _ = c(ctx, cmd) + return cmd +} + +// GetEx An expiration of zero removes the TTL associated with the key (i.e. GETEX key persist). +// Requires Redis >= 6.2.0. +func (c cmdable) GetEx(ctx context.Context, key string, expiration time.Duration) *StringCmd { + args := make([]interface{}, 0, 4) + args = append(args, "getex", key) + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == 0 { + args = append(args, "persist") + } + + cmd := NewStringCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// GetDel redis-server version >= 6.2.0. +func (c cmdable) GetDel(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "getdel", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Incr(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "incr", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) IncrBy(ctx context.Context, key string, value int64) *IntCmd { + cmd := NewIntCmd(ctx, "incrby", key, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) IncrByFloat(ctx context.Context, key string, value float64) *FloatCmd { + cmd := NewFloatCmd(ctx, "incrbyfloat", key, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) MGet(ctx context.Context, keys ...string) *SliceCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "mget" + for i, key := range keys { + args[1+i] = key + } + cmd := NewSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// MSet is like Set but accepts multiple values: +// - MSet("key1", "value1", "key2", "value2") +// - MSet([]string{"key1", "value1", "key2", "value2"}) +// - MSet(map[string]interface{}{"key1": "value1", "key2": "value2"}) +func (c cmdable) MSet(ctx context.Context, values ...interface{}) *StatusCmd { + args := make([]interface{}, 1, 1+len(values)) + args[0] = "mset" + args = appendArgs(args, values) + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// MSetNX is like SetNX but accepts multiple values: +// - MSetNX("key1", "value1", "key2", "value2") +// - MSetNX([]string{"key1", "value1", "key2", "value2"}) +// - MSetNX(map[string]interface{}{"key1": "value1", "key2": "value2"}) +func (c cmdable) MSetNX(ctx context.Context, values ...interface{}) *BoolCmd { + args := make([]interface{}, 1, 1+len(values)) + args[0] = "msetnx" + args = appendArgs(args, values) + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// Set Redis `SET key value [expiration]` command. +// Use expiration for `SETEX`-like behavior. +// +// Zero expiration means the key has no expiration time. +// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, +// otherwise you will receive an error: (error) ERR syntax error. +func (c cmdable) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd { + args := make([]interface{}, 3, 5) + args[0] = "set" + args[1] = key + args[2] = value + if expiration > 0 { + if usePrecise(expiration) { + args = append(args, "px", formatMs(ctx, expiration)) + } else { + args = append(args, "ex", formatSec(ctx, expiration)) + } + } else if expiration == KeepTTL { + args = append(args, "keepttl") + } + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetArgs provides arguments for the SetArgs function. +type SetArgs struct { + // Mode can be `NX` or `XX` or empty. + Mode string + + // Zero `TTL` or `Expiration` means that the key has no expiration time. + TTL time.Duration + ExpireAt time.Time + + // When Get is true, the command returns the old value stored at key, or nil when key did not exist. + Get bool + + // KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, + // otherwise you will receive an error: (error) ERR syntax error. + KeepTTL bool +} + +// SetArgs supports all the options that the SET command supports. +// It is the alternative to the Set function when you want +// to have more control over the options. +func (c cmdable) SetArgs(ctx context.Context, key string, value interface{}, a SetArgs) *StatusCmd { + args := []interface{}{"set", key, value} + + if a.KeepTTL { + args = append(args, "keepttl") + } + + if !a.ExpireAt.IsZero() { + args = append(args, "exat", a.ExpireAt.Unix()) + } + if a.TTL > 0 { + if usePrecise(a.TTL) { + args = append(args, "px", formatMs(ctx, a.TTL)) + } else { + args = append(args, "ex", formatSec(ctx, a.TTL)) + } + } + + if a.Mode != "" { + args = append(args, a.Mode) + } + + if a.Get { + args = append(args, "get") + } + + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SetEX Redis `SETEX key expiration value` command. +func (c cmdable) SetEX(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd { + cmd := NewStatusCmd(ctx, "setex", key, formatSec(ctx, expiration), value) + _ = c(ctx, cmd) + return cmd +} + +// SetNX Redis `SET key value [expiration] NX` command. +// +// Zero expiration means the key has no expiration time. +// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, +// otherwise you will receive an error: (error) ERR syntax error. +func (c cmdable) SetNX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd { + var cmd *BoolCmd + switch expiration { + case 0: + // Use old `SETNX` to support old Redis versions. + cmd = NewBoolCmd(ctx, "setnx", key, value) + case KeepTTL: + cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "nx") + default: + if usePrecise(expiration) { + cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "nx") + } else { + cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "nx") + } + } + + _ = c(ctx, cmd) + return cmd +} + +// SetXX Redis `SET key value [expiration] XX` command. +// +// Zero expiration means the key has no expiration time. +// KeepTTL is a Redis KEEPTTL option to keep existing TTL, it requires your redis-server version >= 6.0, +// otherwise you will receive an error: (error) ERR syntax error. +func (c cmdable) SetXX(ctx context.Context, key string, value interface{}, expiration time.Duration) *BoolCmd { + var cmd *BoolCmd + switch expiration { + case 0: + cmd = NewBoolCmd(ctx, "set", key, value, "xx") + case KeepTTL: + cmd = NewBoolCmd(ctx, "set", key, value, "keepttl", "xx") + default: + if usePrecise(expiration) { + cmd = NewBoolCmd(ctx, "set", key, value, "px", formatMs(ctx, expiration), "xx") + } else { + cmd = NewBoolCmd(ctx, "set", key, value, "ex", formatSec(ctx, expiration), "xx") + } + } + + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SetRange(ctx context.Context, key string, offset int64, value string) *IntCmd { + cmd := NewIntCmd(ctx, "setrange", key, offset, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) StrLen(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "strlen", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) Copy(ctx context.Context, sourceKey, destKey string, db int, replace bool) *IntCmd { + args := []interface{}{"copy", sourceKey, destKey, "DB", db} + if replace { + args = append(args, "REPLACE") + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c cmdable) GetBit(ctx context.Context, key string, offset int64) *IntCmd { + cmd := NewIntCmd(ctx, "getbit", key, offset) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SetBit(ctx context.Context, key string, offset int64, value int) *IntCmd { + cmd := NewIntCmd( + ctx, + "setbit", + key, + offset, + value, + ) + _ = c(ctx, cmd) + return cmd +} + +type BitCount struct { + Start, End int64 +} + +func (c cmdable) BitCount(ctx context.Context, key string, bitCount *BitCount) *IntCmd { + args := []interface{}{"bitcount", key} + if bitCount != nil { + args = append( + args, + bitCount.Start, + bitCount.End, + ) + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) bitOp(ctx context.Context, op, destKey string, keys ...string) *IntCmd { + args := make([]interface{}, 3+len(keys)) + args[0] = "bitop" + args[1] = op + args[2] = destKey + for i, key := range keys { + args[3+i] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) BitOpAnd(ctx context.Context, destKey string, keys ...string) *IntCmd { + return c.bitOp(ctx, "and", destKey, keys...) +} + +func (c cmdable) BitOpOr(ctx context.Context, destKey string, keys ...string) *IntCmd { + return c.bitOp(ctx, "or", destKey, keys...) +} + +func (c cmdable) BitOpXor(ctx context.Context, destKey string, keys ...string) *IntCmd { + return c.bitOp(ctx, "xor", destKey, keys...) +} + +func (c cmdable) BitOpNot(ctx context.Context, destKey string, key string) *IntCmd { + return c.bitOp(ctx, "not", destKey, key) +} + +func (c cmdable) BitPos(ctx context.Context, key string, bit int64, pos ...int64) *IntCmd { + args := make([]interface{}, 3+len(pos)) + args[0] = "bitpos" + args[1] = key + args[2] = bit + switch len(pos) { + case 0: + case 1: + args[3] = pos[0] + case 2: + args[3] = pos[0] + args[4] = pos[1] + default: + panic("too many arguments") + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) BitField(ctx context.Context, key string, args ...interface{}) *IntSliceCmd { + a := make([]interface{}, 0, 2+len(args)) + a = append(a, "bitfield") + a = append(a, key) + a = append(a, args...) + cmd := NewIntSliceCmd(ctx, a...) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c cmdable) Scan(ctx context.Context, cursor uint64, match string, count int64) *ScanCmd { + args := []interface{}{"scan", cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + cmd := NewScanCmd(ctx, c, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ScanType(ctx context.Context, cursor uint64, match string, count int64, keyType string) *ScanCmd { + args := []interface{}{"scan", cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + if keyType != "" { + args = append(args, "type", keyType) + } + cmd := NewScanCmd(ctx, c, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd { + args := []interface{}{"sscan", key, cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + cmd := NewScanCmd(ctx, c, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd { + args := []interface{}{"hscan", key, cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + cmd := NewScanCmd(ctx, c, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZScan(ctx context.Context, key string, cursor uint64, match string, count int64) *ScanCmd { + args := []interface{}{"zscan", key, cursor} + if match != "" { + args = append(args, "match", match) + } + if count > 0 { + args = append(args, "count", count) + } + cmd := NewScanCmd(ctx, c, args...) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c cmdable) HDel(ctx context.Context, key string, fields ...string) *IntCmd { + args := make([]interface{}, 2+len(fields)) + args[0] = "hdel" + args[1] = key + for i, field := range fields { + args[2+i] = field + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HExists(ctx context.Context, key, field string) *BoolCmd { + cmd := NewBoolCmd(ctx, "hexists", key, field) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HGet(ctx context.Context, key, field string) *StringCmd { + cmd := NewStringCmd(ctx, "hget", key, field) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HGetAll(ctx context.Context, key string) *StringStringMapCmd { + cmd := NewStringStringMapCmd(ctx, "hgetall", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HIncrBy(ctx context.Context, key, field string, incr int64) *IntCmd { + cmd := NewIntCmd(ctx, "hincrby", key, field, incr) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HIncrByFloat(ctx context.Context, key, field string, incr float64) *FloatCmd { + cmd := NewFloatCmd(ctx, "hincrbyfloat", key, field, incr) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HKeys(ctx context.Context, key string) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "hkeys", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HLen(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "hlen", key) + _ = c(ctx, cmd) + return cmd +} + +// HMGet returns the values for the specified fields in the hash stored at key. +// It returns an interface{} to distinguish between empty string and nil value. +func (c cmdable) HMGet(ctx context.Context, key string, fields ...string) *SliceCmd { + args := make([]interface{}, 2+len(fields)) + args[0] = "hmget" + args[1] = key + for i, field := range fields { + args[2+i] = field + } + cmd := NewSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// HSet accepts values in following formats: +// - HSet("myhash", "key1", "value1", "key2", "value2") +// - HSet("myhash", []string{"key1", "value1", "key2", "value2"}) +// - HSet("myhash", map[string]interface{}{"key1": "value1", "key2": "value2"}) +// +// Note that it requires Redis v4 for multiple field/value pairs support. +func (c cmdable) HSet(ctx context.Context, key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "hset" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// HMSet is a deprecated version of HSet left for compatibility with Redis 3. +func (c cmdable) HMSet(ctx context.Context, key string, values ...interface{}) *BoolCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "hmset" + args[1] = key + args = appendArgs(args, values) + cmd := NewBoolCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HSetNX(ctx context.Context, key, field string, value interface{}) *BoolCmd { + cmd := NewBoolCmd(ctx, "hsetnx", key, field, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) HVals(ctx context.Context, key string) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "hvals", key) + _ = c(ctx, cmd) + return cmd +} + +// HRandField redis-server version >= 6.2.0. +func (c cmdable) HRandField(ctx context.Context, key string, count int, withValues bool) *StringSliceCmd { + args := make([]interface{}, 0, 4) + + // Although count=0 is meaningless, redis accepts count=0. + args = append(args, "hrandfield", key, count) + if withValues { + args = append(args, "withvalues") + } + + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c cmdable) BLPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)+1) + args[0] = "blpop" + for i, key := range keys { + args[1+i] = key + } + args[len(args)-1] = formatSec(ctx, timeout) + cmd := NewStringSliceCmd(ctx, args...) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) BRPop(ctx context.Context, timeout time.Duration, keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)+1) + args[0] = "brpop" + for i, key := range keys { + args[1+i] = key + } + args[len(keys)+1] = formatSec(ctx, timeout) + cmd := NewStringSliceCmd(ctx, args...) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) BRPopLPush(ctx context.Context, source, destination string, timeout time.Duration) *StringCmd { + cmd := NewStringCmd( + ctx, + "brpoplpush", + source, + destination, + formatSec(ctx, timeout), + ) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LIndex(ctx context.Context, key string, index int64) *StringCmd { + cmd := NewStringCmd(ctx, "lindex", key, index) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LInsert(ctx context.Context, key, op string, pivot, value interface{}) *IntCmd { + cmd := NewIntCmd(ctx, "linsert", key, op, pivot, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LInsertBefore(ctx context.Context, key string, pivot, value interface{}) *IntCmd { + cmd := NewIntCmd(ctx, "linsert", key, "before", pivot, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LInsertAfter(ctx context.Context, key string, pivot, value interface{}) *IntCmd { + cmd := NewIntCmd(ctx, "linsert", key, "after", pivot, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LLen(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "llen", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LPop(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "lpop", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LPopCount(ctx context.Context, key string, count int) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "lpop", key, count) + _ = c(ctx, cmd) + return cmd +} + +type LPosArgs struct { + Rank, MaxLen int64 +} + +func (c cmdable) LPos(ctx context.Context, key string, value string, a LPosArgs) *IntCmd { + args := []interface{}{"lpos", key, value} + if a.Rank != 0 { + args = append(args, "rank", a.Rank) + } + if a.MaxLen != 0 { + args = append(args, "maxlen", a.MaxLen) + } + + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LPosCount(ctx context.Context, key string, value string, count int64, a LPosArgs) *IntSliceCmd { + args := []interface{}{"lpos", key, value, "count", count} + if a.Rank != 0 { + args = append(args, "rank", a.Rank) + } + if a.MaxLen != 0 { + args = append(args, "maxlen", a.MaxLen) + } + cmd := NewIntSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LPush(ctx context.Context, key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "lpush" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LPushX(ctx context.Context, key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "lpushx" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd { + cmd := NewStringSliceCmd( + ctx, + "lrange", + key, + start, + stop, + ) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LRem(ctx context.Context, key string, count int64, value interface{}) *IntCmd { + cmd := NewIntCmd(ctx, "lrem", key, count, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LSet(ctx context.Context, key string, index int64, value interface{}) *StatusCmd { + cmd := NewStatusCmd(ctx, "lset", key, index, value) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LTrim(ctx context.Context, key string, start, stop int64) *StatusCmd { + cmd := NewStatusCmd( + ctx, + "ltrim", + key, + start, + stop, + ) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) RPop(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "rpop", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) RPopCount(ctx context.Context, key string, count int) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "rpop", key, count) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) RPopLPush(ctx context.Context, source, destination string) *StringCmd { + cmd := NewStringCmd(ctx, "rpoplpush", source, destination) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) RPush(ctx context.Context, key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "rpush" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) RPushX(ctx context.Context, key string, values ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(values)) + args[0] = "rpushx" + args[1] = key + args = appendArgs(args, values) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) LMove(ctx context.Context, source, destination, srcpos, destpos string) *StringCmd { + cmd := NewStringCmd(ctx, "lmove", source, destination, srcpos, destpos) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) BLMove( + ctx context.Context, source, destination, srcpos, destpos string, timeout time.Duration, +) *StringCmd { + cmd := NewStringCmd(ctx, "blmove", source, destination, srcpos, destpos, formatSec(ctx, timeout)) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c cmdable) SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(members)) + args[0] = "sadd" + args[1] = key + args = appendArgs(args, members) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SCard(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "scard", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SDiff(ctx context.Context, keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "sdiff" + for i, key := range keys { + args[1+i] = key + } + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "sdiffstore" + args[1] = destination + for i, key := range keys { + args[2+i] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SInter(ctx context.Context, keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "sinter" + for i, key := range keys { + args[1+i] = key + } + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SInterStore(ctx context.Context, destination string, keys ...string) *IntCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "sinterstore" + args[1] = destination + for i, key := range keys { + args[2+i] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SIsMember(ctx context.Context, key string, member interface{}) *BoolCmd { + cmd := NewBoolCmd(ctx, "sismember", key, member) + _ = c(ctx, cmd) + return cmd +} + +// SMIsMember Redis `SMISMEMBER key member [member ...]` command. +func (c cmdable) SMIsMember(ctx context.Context, key string, members ...interface{}) *BoolSliceCmd { + args := make([]interface{}, 2, 2+len(members)) + args[0] = "smismember" + args[1] = key + args = appendArgs(args, members) + cmd := NewBoolSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// SMembers Redis `SMEMBERS key` command output as a slice. +func (c cmdable) SMembers(ctx context.Context, key string) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "smembers", key) + _ = c(ctx, cmd) + return cmd +} + +// SMembersMap Redis `SMEMBERS key` command output as a map. +func (c cmdable) SMembersMap(ctx context.Context, key string) *StringStructMapCmd { + cmd := NewStringStructMapCmd(ctx, "smembers", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SMove(ctx context.Context, source, destination string, member interface{}) *BoolCmd { + cmd := NewBoolCmd(ctx, "smove", source, destination, member) + _ = c(ctx, cmd) + return cmd +} + +// SPop Redis `SPOP key` command. +func (c cmdable) SPop(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "spop", key) + _ = c(ctx, cmd) + return cmd +} + +// SPopN Redis `SPOP key count` command. +func (c cmdable) SPopN(ctx context.Context, key string, count int64) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "spop", key, count) + _ = c(ctx, cmd) + return cmd +} + +// SRandMember Redis `SRANDMEMBER key` command. +func (c cmdable) SRandMember(ctx context.Context, key string) *StringCmd { + cmd := NewStringCmd(ctx, "srandmember", key) + _ = c(ctx, cmd) + return cmd +} + +// SRandMemberN Redis `SRANDMEMBER key count` command. +func (c cmdable) SRandMemberN(ctx context.Context, key string, count int64) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "srandmember", key, count) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SRem(ctx context.Context, key string, members ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(members)) + args[0] = "srem" + args[1] = key + args = appendArgs(args, members) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SUnion(ctx context.Context, keys ...string) *StringSliceCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "sunion" + for i, key := range keys { + args[1+i] = key + } + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) SUnionStore(ctx context.Context, destination string, keys ...string) *IntCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "sunionstore" + args[1] = destination + for i, key := range keys { + args[2+i] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +// XAddArgs accepts values in the following formats: +// - XAddArgs.Values = []interface{}{"key1", "value1", "key2", "value2"} +// - XAddArgs.Values = []string("key1", "value1", "key2", "value2") +// - XAddArgs.Values = map[string]interface{}{"key1": "value1", "key2": "value2"} +// +// Note that map will not preserve the order of key-value pairs. +// MaxLen/MaxLenApprox and MinID are in conflict, only one of them can be used. +type XAddArgs struct { + Stream string + NoMkStream bool + MaxLen int64 // MAXLEN N + + // Deprecated: use MaxLen+Approx, remove in v9. + MaxLenApprox int64 // MAXLEN ~ N + + MinID string + // Approx causes MaxLen and MinID to use "~" matcher (instead of "="). + Approx bool + Limit int64 + ID string + Values interface{} +} + +// XAdd a.Limit has a bug, please confirm it and use it. +// issue: https://github.com/redis/redis/issues/9046 +func (c cmdable) XAdd(ctx context.Context, a *XAddArgs) *StringCmd { + args := make([]interface{}, 0, 11) + args = append(args, "xadd", a.Stream) + if a.NoMkStream { + args = append(args, "nomkstream") + } + switch { + case a.MaxLen > 0: + if a.Approx { + args = append(args, "maxlen", "~", a.MaxLen) + } else { + args = append(args, "maxlen", a.MaxLen) + } + case a.MaxLenApprox > 0: + // TODO remove in v9. + args = append(args, "maxlen", "~", a.MaxLenApprox) + case a.MinID != "": + if a.Approx { + args = append(args, "minid", "~", a.MinID) + } else { + args = append(args, "minid", a.MinID) + } + } + if a.Limit > 0 { + args = append(args, "limit", a.Limit) + } + if a.ID != "" { + args = append(args, a.ID) + } else { + args = append(args, "*") + } + args = appendArg(args, a.Values) + + cmd := NewStringCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XDel(ctx context.Context, stream string, ids ...string) *IntCmd { + args := []interface{}{"xdel", stream} + for _, id := range ids { + args = append(args, id) + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XLen(ctx context.Context, stream string) *IntCmd { + cmd := NewIntCmd(ctx, "xlen", stream) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd { + cmd := NewXMessageSliceCmd(ctx, "xrange", stream, start, stop) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd { + cmd := NewXMessageSliceCmd(ctx, "xrange", stream, start, stop, "count", count) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XRevRange(ctx context.Context, stream, start, stop string) *XMessageSliceCmd { + cmd := NewXMessageSliceCmd(ctx, "xrevrange", stream, start, stop) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XRevRangeN(ctx context.Context, stream, start, stop string, count int64) *XMessageSliceCmd { + cmd := NewXMessageSliceCmd(ctx, "xrevrange", stream, start, stop, "count", count) + _ = c(ctx, cmd) + return cmd +} + +type XReadArgs struct { + Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2 + Count int64 + Block time.Duration +} + +func (c cmdable) XRead(ctx context.Context, a *XReadArgs) *XStreamSliceCmd { + args := make([]interface{}, 0, 6+len(a.Streams)) + args = append(args, "xread") + + keyPos := int8(1) + if a.Count > 0 { + args = append(args, "count") + args = append(args, a.Count) + keyPos += 2 + } + if a.Block >= 0 { + args = append(args, "block") + args = append(args, int64(a.Block/time.Millisecond)) + keyPos += 2 + } + args = append(args, "streams") + keyPos++ + for _, s := range a.Streams { + args = append(args, s) + } + + cmd := NewXStreamSliceCmd(ctx, args...) + if a.Block >= 0 { + cmd.setReadTimeout(a.Block) + } + cmd.SetFirstKeyPos(keyPos) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XReadStreams(ctx context.Context, streams ...string) *XStreamSliceCmd { + return c.XRead(ctx, &XReadArgs{ + Streams: streams, + Block: -1, + }) +} + +func (c cmdable) XGroupCreate(ctx context.Context, stream, group, start string) *StatusCmd { + cmd := NewStatusCmd(ctx, "xgroup", "create", stream, group, start) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XGroupCreateMkStream(ctx context.Context, stream, group, start string) *StatusCmd { + cmd := NewStatusCmd(ctx, "xgroup", "create", stream, group, start, "mkstream") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XGroupSetID(ctx context.Context, stream, group, start string) *StatusCmd { + cmd := NewStatusCmd(ctx, "xgroup", "setid", stream, group, start) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XGroupDestroy(ctx context.Context, stream, group string) *IntCmd { + cmd := NewIntCmd(ctx, "xgroup", "destroy", stream, group) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XGroupCreateConsumer(ctx context.Context, stream, group, consumer string) *IntCmd { + cmd := NewIntCmd(ctx, "xgroup", "createconsumer", stream, group, consumer) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XGroupDelConsumer(ctx context.Context, stream, group, consumer string) *IntCmd { + cmd := NewIntCmd(ctx, "xgroup", "delconsumer", stream, group, consumer) + _ = c(ctx, cmd) + return cmd +} + +type XReadGroupArgs struct { + Group string + Consumer string + Streams []string // list of streams and ids, e.g. stream1 stream2 id1 id2 + Count int64 + Block time.Duration + NoAck bool +} + +func (c cmdable) XReadGroup(ctx context.Context, a *XReadGroupArgs) *XStreamSliceCmd { + args := make([]interface{}, 0, 10+len(a.Streams)) + args = append(args, "xreadgroup", "group", a.Group, a.Consumer) + + keyPos := int8(4) + if a.Count > 0 { + args = append(args, "count", a.Count) + keyPos += 2 + } + if a.Block >= 0 { + args = append(args, "block", int64(a.Block/time.Millisecond)) + keyPos += 2 + } + if a.NoAck { + args = append(args, "noack") + keyPos++ + } + args = append(args, "streams") + keyPos++ + for _, s := range a.Streams { + args = append(args, s) + } + + cmd := NewXStreamSliceCmd(ctx, args...) + if a.Block >= 0 { + cmd.setReadTimeout(a.Block) + } + cmd.SetFirstKeyPos(keyPos) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XAck(ctx context.Context, stream, group string, ids ...string) *IntCmd { + args := []interface{}{"xack", stream, group} + for _, id := range ids { + args = append(args, id) + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XPending(ctx context.Context, stream, group string) *XPendingCmd { + cmd := NewXPendingCmd(ctx, "xpending", stream, group) + _ = c(ctx, cmd) + return cmd +} + +type XPendingExtArgs struct { + Stream string + Group string + Idle time.Duration + Start string + End string + Count int64 + Consumer string +} + +func (c cmdable) XPendingExt(ctx context.Context, a *XPendingExtArgs) *XPendingExtCmd { + args := make([]interface{}, 0, 9) + args = append(args, "xpending", a.Stream, a.Group) + if a.Idle != 0 { + args = append(args, "idle", formatMs(ctx, a.Idle)) + } + args = append(args, a.Start, a.End, a.Count) + if a.Consumer != "" { + args = append(args, a.Consumer) + } + cmd := NewXPendingExtCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +type XAutoClaimArgs struct { + Stream string + Group string + MinIdle time.Duration + Start string + Count int64 + Consumer string +} + +func (c cmdable) XAutoClaim(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimCmd { + args := xAutoClaimArgs(ctx, a) + cmd := NewXAutoClaimCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XAutoClaimJustID(ctx context.Context, a *XAutoClaimArgs) *XAutoClaimJustIDCmd { + args := xAutoClaimArgs(ctx, a) + args = append(args, "justid") + cmd := NewXAutoClaimJustIDCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func xAutoClaimArgs(ctx context.Context, a *XAutoClaimArgs) []interface{} { + args := make([]interface{}, 0, 8) + args = append(args, "xautoclaim", a.Stream, a.Group, a.Consumer, formatMs(ctx, a.MinIdle), a.Start) + if a.Count > 0 { + args = append(args, "count", a.Count) + } + return args +} + +type XClaimArgs struct { + Stream string + Group string + Consumer string + MinIdle time.Duration + Messages []string +} + +func (c cmdable) XClaim(ctx context.Context, a *XClaimArgs) *XMessageSliceCmd { + args := xClaimArgs(a) + cmd := NewXMessageSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XClaimJustID(ctx context.Context, a *XClaimArgs) *StringSliceCmd { + args := xClaimArgs(a) + args = append(args, "justid") + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func xClaimArgs(a *XClaimArgs) []interface{} { + args := make([]interface{}, 0, 5+len(a.Messages)) + args = append(args, + "xclaim", + a.Stream, + a.Group, a.Consumer, + int64(a.MinIdle/time.Millisecond)) + for _, id := range a.Messages { + args = append(args, id) + } + return args +} + +// xTrim If approx is true, add the "~" parameter, otherwise it is the default "=" (redis default). +// example: +// XTRIM key MAXLEN/MINID threshold LIMIT limit. +// XTRIM key MAXLEN/MINID ~ threshold LIMIT limit. +// The redis-server version is lower than 6.2, please set limit to 0. +func (c cmdable) xTrim( + ctx context.Context, key, strategy string, + approx bool, threshold interface{}, limit int64, +) *IntCmd { + args := make([]interface{}, 0, 7) + args = append(args, "xtrim", key, strategy) + if approx { + args = append(args, "~") + } + args = append(args, threshold) + if limit > 0 { + args = append(args, "limit", limit) + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// Deprecated: use XTrimMaxLen, remove in v9. +func (c cmdable) XTrim(ctx context.Context, key string, maxLen int64) *IntCmd { + return c.xTrim(ctx, key, "maxlen", false, maxLen, 0) +} + +// Deprecated: use XTrimMaxLenApprox, remove in v9. +func (c cmdable) XTrimApprox(ctx context.Context, key string, maxLen int64) *IntCmd { + return c.xTrim(ctx, key, "maxlen", true, maxLen, 0) +} + +// XTrimMaxLen No `~` rules are used, `limit` cannot be used. +// cmd: XTRIM key MAXLEN maxLen +func (c cmdable) XTrimMaxLen(ctx context.Context, key string, maxLen int64) *IntCmd { + return c.xTrim(ctx, key, "maxlen", false, maxLen, 0) +} + +// XTrimMaxLenApprox LIMIT has a bug, please confirm it and use it. +// issue: https://github.com/redis/redis/issues/9046 +// cmd: XTRIM key MAXLEN ~ maxLen LIMIT limit +func (c cmdable) XTrimMaxLenApprox(ctx context.Context, key string, maxLen, limit int64) *IntCmd { + return c.xTrim(ctx, key, "maxlen", true, maxLen, limit) +} + +// XTrimMinID No `~` rules are used, `limit` cannot be used. +// cmd: XTRIM key MINID minID +func (c cmdable) XTrimMinID(ctx context.Context, key string, minID string) *IntCmd { + return c.xTrim(ctx, key, "minid", false, minID, 0) +} + +// XTrimMinIDApprox LIMIT has a bug, please confirm it and use it. +// issue: https://github.com/redis/redis/issues/9046 +// cmd: XTRIM key MINID ~ minID LIMIT limit +func (c cmdable) XTrimMinIDApprox(ctx context.Context, key string, minID string, limit int64) *IntCmd { + return c.xTrim(ctx, key, "minid", true, minID, limit) +} + +func (c cmdable) XInfoConsumers(ctx context.Context, key string, group string) *XInfoConsumersCmd { + cmd := NewXInfoConsumersCmd(ctx, key, group) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XInfoGroups(ctx context.Context, key string) *XInfoGroupsCmd { + cmd := NewXInfoGroupsCmd(ctx, key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) XInfoStream(ctx context.Context, key string) *XInfoStreamCmd { + cmd := NewXInfoStreamCmd(ctx, key) + _ = c(ctx, cmd) + return cmd +} + +// XInfoStreamFull XINFO STREAM FULL [COUNT count] +// redis-server >= 6.0. +func (c cmdable) XInfoStreamFull(ctx context.Context, key string, count int) *XInfoStreamFullCmd { + args := make([]interface{}, 0, 6) + args = append(args, "xinfo", "stream", key, "full") + if count > 0 { + args = append(args, "count", count) + } + cmd := NewXInfoStreamFullCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +// Z represents sorted set member. +type Z struct { + Score float64 + Member interface{} +} + +// ZWithKey represents sorted set member including the name of the key where it was popped. +type ZWithKey struct { + Z + Key string +} + +// ZStore is used as an arg to ZInter/ZInterStore and ZUnion/ZUnionStore. +type ZStore struct { + Keys []string + Weights []float64 + // Can be SUM, MIN or MAX. + Aggregate string +} + +func (z ZStore) len() (n int) { + n = len(z.Keys) + if len(z.Weights) > 0 { + n += 1 + len(z.Weights) + } + if z.Aggregate != "" { + n += 2 + } + return n +} + +func (z ZStore) appendArgs(args []interface{}) []interface{} { + for _, key := range z.Keys { + args = append(args, key) + } + if len(z.Weights) > 0 { + args = append(args, "weights") + for _, weights := range z.Weights { + args = append(args, weights) + } + } + if z.Aggregate != "" { + args = append(args, "aggregate", z.Aggregate) + } + return args +} + +// BZPopMax Redis `BZPOPMAX key [key ...] timeout` command. +func (c cmdable) BZPopMax(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd { + args := make([]interface{}, 1+len(keys)+1) + args[0] = "bzpopmax" + for i, key := range keys { + args[1+i] = key + } + args[len(args)-1] = formatSec(ctx, timeout) + cmd := NewZWithKeyCmd(ctx, args...) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + +// BZPopMin Redis `BZPOPMIN key [key ...] timeout` command. +func (c cmdable) BZPopMin(ctx context.Context, timeout time.Duration, keys ...string) *ZWithKeyCmd { + args := make([]interface{}, 1+len(keys)+1) + args[0] = "bzpopmin" + for i, key := range keys { + args[1+i] = key + } + args[len(args)-1] = formatSec(ctx, timeout) + cmd := NewZWithKeyCmd(ctx, args...) + cmd.setReadTimeout(timeout) + _ = c(ctx, cmd) + return cmd +} + +// ZAddArgs WARN: The GT, LT and NX options are mutually exclusive. +type ZAddArgs struct { + NX bool + XX bool + LT bool + GT bool + Ch bool + Members []Z +} + +func (c cmdable) zAddArgs(key string, args ZAddArgs, incr bool) []interface{} { + a := make([]interface{}, 0, 6+2*len(args.Members)) + a = append(a, "zadd", key) + + // The GT, LT and NX options are mutually exclusive. + if args.NX { + a = append(a, "nx") + } else { + if args.XX { + a = append(a, "xx") + } + if args.GT { + a = append(a, "gt") + } else if args.LT { + a = append(a, "lt") + } + } + if args.Ch { + a = append(a, "ch") + } + if incr { + a = append(a, "incr") + } + for _, m := range args.Members { + a = append(a, m.Score) + a = append(a, m.Member) + } + return a +} + +func (c cmdable) ZAddArgs(ctx context.Context, key string, args ZAddArgs) *IntCmd { + cmd := NewIntCmd(ctx, c.zAddArgs(key, args, false)...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZAddArgsIncr(ctx context.Context, key string, args ZAddArgs) *FloatCmd { + cmd := NewFloatCmd(ctx, c.zAddArgs(key, args, true)...) + _ = c(ctx, cmd) + return cmd +} + +// TODO: Compatible with v8 api, will be removed in v9. +func (c cmdable) zAdd(ctx context.Context, key string, args ZAddArgs, members ...*Z) *IntCmd { + args.Members = make([]Z, len(members)) + for i, m := range members { + args.Members[i] = *m + } + cmd := NewIntCmd(ctx, c.zAddArgs(key, args, false)...) + _ = c(ctx, cmd) + return cmd +} + +// ZAdd Redis `ZADD key score member [score member ...]` command. +func (c cmdable) ZAdd(ctx context.Context, key string, members ...*Z) *IntCmd { + return c.zAdd(ctx, key, ZAddArgs{}, members...) +} + +// ZAddNX Redis `ZADD key NX score member [score member ...]` command. +func (c cmdable) ZAddNX(ctx context.Context, key string, members ...*Z) *IntCmd { + return c.zAdd(ctx, key, ZAddArgs{ + NX: true, + }, members...) +} + +// ZAddXX Redis `ZADD key XX score member [score member ...]` command. +func (c cmdable) ZAddXX(ctx context.Context, key string, members ...*Z) *IntCmd { + return c.zAdd(ctx, key, ZAddArgs{ + XX: true, + }, members...) +} + +// ZAddCh Redis `ZADD key CH score member [score member ...]` command. +// Deprecated: Use +// client.ZAddArgs(ctx, ZAddArgs{ +// Ch: true, +// Members: []Z, +// }) +// remove in v9. +func (c cmdable) ZAddCh(ctx context.Context, key string, members ...*Z) *IntCmd { + return c.zAdd(ctx, key, ZAddArgs{ + Ch: true, + }, members...) +} + +// ZAddNXCh Redis `ZADD key NX CH score member [score member ...]` command. +// Deprecated: Use +// client.ZAddArgs(ctx, ZAddArgs{ +// NX: true, +// Ch: true, +// Members: []Z, +// }) +// remove in v9. +func (c cmdable) ZAddNXCh(ctx context.Context, key string, members ...*Z) *IntCmd { + return c.zAdd(ctx, key, ZAddArgs{ + NX: true, + Ch: true, + }, members...) +} + +// ZAddXXCh Redis `ZADD key XX CH score member [score member ...]` command. +// Deprecated: Use +// client.ZAddArgs(ctx, ZAddArgs{ +// XX: true, +// Ch: true, +// Members: []Z, +// }) +// remove in v9. +func (c cmdable) ZAddXXCh(ctx context.Context, key string, members ...*Z) *IntCmd { + return c.zAdd(ctx, key, ZAddArgs{ + XX: true, + Ch: true, + }, members...) +} + +// ZIncr Redis `ZADD key INCR score member` command. +// Deprecated: Use +// client.ZAddArgsIncr(ctx, ZAddArgs{ +// Members: []Z, +// }) +// remove in v9. +func (c cmdable) ZIncr(ctx context.Context, key string, member *Z) *FloatCmd { + return c.ZAddArgsIncr(ctx, key, ZAddArgs{ + Members: []Z{*member}, + }) +} + +// ZIncrNX Redis `ZADD key NX INCR score member` command. +// Deprecated: Use +// client.ZAddArgsIncr(ctx, ZAddArgs{ +// NX: true, +// Members: []Z, +// }) +// remove in v9. +func (c cmdable) ZIncrNX(ctx context.Context, key string, member *Z) *FloatCmd { + return c.ZAddArgsIncr(ctx, key, ZAddArgs{ + NX: true, + Members: []Z{*member}, + }) +} + +// ZIncrXX Redis `ZADD key XX INCR score member` command. +// Deprecated: Use +// client.ZAddArgsIncr(ctx, ZAddArgs{ +// XX: true, +// Members: []Z, +// }) +// remove in v9. +func (c cmdable) ZIncrXX(ctx context.Context, key string, member *Z) *FloatCmd { + return c.ZAddArgsIncr(ctx, key, ZAddArgs{ + XX: true, + Members: []Z{*member}, + }) +} + +func (c cmdable) ZCard(ctx context.Context, key string) *IntCmd { + cmd := NewIntCmd(ctx, "zcard", key) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZCount(ctx context.Context, key, min, max string) *IntCmd { + cmd := NewIntCmd(ctx, "zcount", key, min, max) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZLexCount(ctx context.Context, key, min, max string) *IntCmd { + cmd := NewIntCmd(ctx, "zlexcount", key, min, max) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZIncrBy(ctx context.Context, key string, increment float64, member string) *FloatCmd { + cmd := NewFloatCmd(ctx, "zincrby", key, increment, member) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZInterStore(ctx context.Context, destination string, store *ZStore) *IntCmd { + args := make([]interface{}, 0, 3+store.len()) + args = append(args, "zinterstore", destination, len(store.Keys)) + args = store.appendArgs(args) + cmd := NewIntCmd(ctx, args...) + cmd.SetFirstKeyPos(3) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZInter(ctx context.Context, store *ZStore) *StringSliceCmd { + args := make([]interface{}, 0, 2+store.len()) + args = append(args, "zinter", len(store.Keys)) + args = store.appendArgs(args) + cmd := NewStringSliceCmd(ctx, args...) + cmd.SetFirstKeyPos(2) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZInterWithScores(ctx context.Context, store *ZStore) *ZSliceCmd { + args := make([]interface{}, 0, 3+store.len()) + args = append(args, "zinter", len(store.Keys)) + args = store.appendArgs(args) + args = append(args, "withscores") + cmd := NewZSliceCmd(ctx, args...) + cmd.SetFirstKeyPos(2) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZMScore(ctx context.Context, key string, members ...string) *FloatSliceCmd { + args := make([]interface{}, 2+len(members)) + args[0] = "zmscore" + args[1] = key + for i, member := range members { + args[2+i] = member + } + cmd := NewFloatSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZPopMax(ctx context.Context, key string, count ...int64) *ZSliceCmd { + args := []interface{}{ + "zpopmax", + key, + } + + switch len(count) { + case 0: + break + case 1: + args = append(args, count[0]) + default: + panic("too many arguments") + } + + cmd := NewZSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZPopMin(ctx context.Context, key string, count ...int64) *ZSliceCmd { + args := []interface{}{ + "zpopmin", + key, + } + + switch len(count) { + case 0: + break + case 1: + args = append(args, count[0]) + default: + panic("too many arguments") + } + + cmd := NewZSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// ZRangeArgs is all the options of the ZRange command. +// In version> 6.2.0, you can replace the(cmd): +// ZREVRANGE, +// ZRANGEBYSCORE, +// ZREVRANGEBYSCORE, +// ZRANGEBYLEX, +// ZREVRANGEBYLEX. +// Please pay attention to your redis-server version. +// +// Rev, ByScore, ByLex and Offset+Count options require redis-server 6.2.0 and higher. +type ZRangeArgs struct { + Key string + + // When the ByScore option is provided, the open interval(exclusive) can be set. + // By default, the score intervals specified by and are closed (inclusive). + // It is similar to the deprecated(6.2.0+) ZRangeByScore command. + // For example: + // ZRangeArgs{ + // Key: "example-key", + // Start: "(3", + // Stop: 8, + // ByScore: true, + // } + // cmd: "ZRange example-key (3 8 ByScore" (3 < score <= 8). + // + // For the ByLex option, it is similar to the deprecated(6.2.0+) ZRangeByLex command. + // You can set the and options as follows: + // ZRangeArgs{ + // Key: "example-key", + // Start: "[abc", + // Stop: "(def", + // ByLex: true, + // } + // cmd: "ZRange example-key [abc (def ByLex" + // + // For normal cases (ByScore==false && ByLex==false), and should be set to the index range (int). + // You can read the documentation for more information: https://redis.io/commands/zrange + Start interface{} + Stop interface{} + + // The ByScore and ByLex options are mutually exclusive. + ByScore bool + ByLex bool + + Rev bool + + // limit offset count. + Offset int64 + Count int64 +} + +func (z ZRangeArgs) appendArgs(args []interface{}) []interface{} { + // For Rev+ByScore/ByLex, we need to adjust the position of and . + if z.Rev && (z.ByScore || z.ByLex) { + args = append(args, z.Key, z.Stop, z.Start) + } else { + args = append(args, z.Key, z.Start, z.Stop) + } + + if z.ByScore { + args = append(args, "byscore") + } else if z.ByLex { + args = append(args, "bylex") + } + if z.Rev { + args = append(args, "rev") + } + if z.Offset != 0 || z.Count != 0 { + args = append(args, "limit", z.Offset, z.Count) + } + return args +} + +func (c cmdable) ZRangeArgs(ctx context.Context, z ZRangeArgs) *StringSliceCmd { + args := make([]interface{}, 0, 9) + args = append(args, "zrange") + args = z.appendArgs(args) + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRangeArgsWithScores(ctx context.Context, z ZRangeArgs) *ZSliceCmd { + args := make([]interface{}, 0, 10) + args = append(args, "zrange") + args = z.appendArgs(args) + args = append(args, "withscores") + cmd := NewZSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd { + return c.ZRangeArgs(ctx, ZRangeArgs{ + Key: key, + Start: start, + Stop: stop, + }) +} + +func (c cmdable) ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd { + return c.ZRangeArgsWithScores(ctx, ZRangeArgs{ + Key: key, + Start: start, + Stop: stop, + }) +} + +type ZRangeBy struct { + Min, Max string + Offset, Count int64 +} + +func (c cmdable) zRangeBy(ctx context.Context, zcmd, key string, opt *ZRangeBy, withScores bool) *StringSliceCmd { + args := []interface{}{zcmd, key, opt.Min, opt.Max} + if withScores { + args = append(args, "withscores") + } + if opt.Offset != 0 || opt.Count != 0 { + args = append( + args, + "limit", + opt.Offset, + opt.Count, + ) + } + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd { + return c.zRangeBy(ctx, "zrangebyscore", key, opt, false) +} + +func (c cmdable) ZRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd { + return c.zRangeBy(ctx, "zrangebylex", key, opt, false) +} + +func (c cmdable) ZRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd { + args := []interface{}{"zrangebyscore", key, opt.Min, opt.Max, "withscores"} + if opt.Offset != 0 || opt.Count != 0 { + args = append( + args, + "limit", + opt.Offset, + opt.Count, + ) + } + cmd := NewZSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRangeStore(ctx context.Context, dst string, z ZRangeArgs) *IntCmd { + args := make([]interface{}, 0, 10) + args = append(args, "zrangestore", dst) + args = z.appendArgs(args) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRank(ctx context.Context, key, member string) *IntCmd { + cmd := NewIntCmd(ctx, "zrank", key, member) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRem(ctx context.Context, key string, members ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(members)) + args[0] = "zrem" + args[1] = key + args = appendArgs(args, members) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRemRangeByRank(ctx context.Context, key string, start, stop int64) *IntCmd { + cmd := NewIntCmd( + ctx, + "zremrangebyrank", + key, + start, + stop, + ) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRemRangeByScore(ctx context.Context, key, min, max string) *IntCmd { + cmd := NewIntCmd(ctx, "zremrangebyscore", key, min, max) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRemRangeByLex(ctx context.Context, key, min, max string) *IntCmd { + cmd := NewIntCmd(ctx, "zremrangebylex", key, min, max) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRevRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd { + cmd := NewStringSliceCmd(ctx, "zrevrange", key, start, stop) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRevRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd { + cmd := NewZSliceCmd(ctx, "zrevrange", key, start, stop, "withscores") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) zRevRangeBy(ctx context.Context, zcmd, key string, opt *ZRangeBy) *StringSliceCmd { + args := []interface{}{zcmd, key, opt.Max, opt.Min} + if opt.Offset != 0 || opt.Count != 0 { + args = append( + args, + "limit", + opt.Offset, + opt.Count, + ) + } + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRevRangeByScore(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd { + return c.zRevRangeBy(ctx, "zrevrangebyscore", key, opt) +} + +func (c cmdable) ZRevRangeByLex(ctx context.Context, key string, opt *ZRangeBy) *StringSliceCmd { + return c.zRevRangeBy(ctx, "zrevrangebylex", key, opt) +} + +func (c cmdable) ZRevRangeByScoreWithScores(ctx context.Context, key string, opt *ZRangeBy) *ZSliceCmd { + args := []interface{}{"zrevrangebyscore", key, opt.Max, opt.Min, "withscores"} + if opt.Offset != 0 || opt.Count != 0 { + args = append( + args, + "limit", + opt.Offset, + opt.Count, + ) + } + cmd := NewZSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZRevRank(ctx context.Context, key, member string) *IntCmd { + cmd := NewIntCmd(ctx, "zrevrank", key, member) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZScore(ctx context.Context, key, member string) *FloatCmd { + cmd := NewFloatCmd(ctx, "zscore", key, member) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZUnion(ctx context.Context, store ZStore) *StringSliceCmd { + args := make([]interface{}, 0, 2+store.len()) + args = append(args, "zunion", len(store.Keys)) + args = store.appendArgs(args) + cmd := NewStringSliceCmd(ctx, args...) + cmd.SetFirstKeyPos(2) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZUnionWithScores(ctx context.Context, store ZStore) *ZSliceCmd { + args := make([]interface{}, 0, 3+store.len()) + args = append(args, "zunion", len(store.Keys)) + args = store.appendArgs(args) + args = append(args, "withscores") + cmd := NewZSliceCmd(ctx, args...) + cmd.SetFirstKeyPos(2) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ZUnionStore(ctx context.Context, dest string, store *ZStore) *IntCmd { + args := make([]interface{}, 0, 3+store.len()) + args = append(args, "zunionstore", dest, len(store.Keys)) + args = store.appendArgs(args) + cmd := NewIntCmd(ctx, args...) + cmd.SetFirstKeyPos(3) + _ = c(ctx, cmd) + return cmd +} + +// ZRandMember redis-server version >= 6.2.0. +func (c cmdable) ZRandMember(ctx context.Context, key string, count int, withScores bool) *StringSliceCmd { + args := make([]interface{}, 0, 4) + + // Although count=0 is meaningless, redis accepts count=0. + args = append(args, "zrandmember", key, count) + if withScores { + args = append(args, "withscores") + } + + cmd := NewStringSliceCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +// ZDiff redis-server version >= 6.2.0. +func (c cmdable) ZDiff(ctx context.Context, keys ...string) *StringSliceCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "zdiff" + args[1] = len(keys) + for i, key := range keys { + args[i+2] = key + } + + cmd := NewStringSliceCmd(ctx, args...) + cmd.SetFirstKeyPos(2) + _ = c(ctx, cmd) + return cmd +} + +// ZDiffWithScores redis-server version >= 6.2.0. +func (c cmdable) ZDiffWithScores(ctx context.Context, keys ...string) *ZSliceCmd { + args := make([]interface{}, 3+len(keys)) + args[0] = "zdiff" + args[1] = len(keys) + for i, key := range keys { + args[i+2] = key + } + args[len(keys)+2] = "withscores" + + cmd := NewZSliceCmd(ctx, args...) + cmd.SetFirstKeyPos(2) + _ = c(ctx, cmd) + return cmd +} + +// ZDiffStore redis-server version >=6.2.0. +func (c cmdable) ZDiffStore(ctx context.Context, destination string, keys ...string) *IntCmd { + args := make([]interface{}, 0, 3+len(keys)) + args = append(args, "zdiffstore", destination, len(keys)) + for _, key := range keys { + args = append(args, key) + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c cmdable) PFAdd(ctx context.Context, key string, els ...interface{}) *IntCmd { + args := make([]interface{}, 2, 2+len(els)) + args[0] = "pfadd" + args[1] = key + args = appendArgs(args, els) + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) PFCount(ctx context.Context, keys ...string) *IntCmd { + args := make([]interface{}, 1+len(keys)) + args[0] = "pfcount" + for i, key := range keys { + args[1+i] = key + } + cmd := NewIntCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) PFMerge(ctx context.Context, dest string, keys ...string) *StatusCmd { + args := make([]interface{}, 2+len(keys)) + args[0] = "pfmerge" + args[1] = dest + for i, key := range keys { + args[2+i] = key + } + cmd := NewStatusCmd(ctx, args...) + _ = c(ctx, cmd) + return cmd +} + +//------------------------------------------------------------------------------ + +func (c cmdable) BgRewriteAOF(ctx context.Context) *StatusCmd { + cmd := NewStatusCmd(ctx, "bgrewriteaof") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) BgSave(ctx context.Context) *StatusCmd { + cmd := NewStatusCmd(ctx, "bgsave") + _ = c(ctx, cmd) + return cmd +} + +func (c cmdable) ClientKill(ctx context.Context, ipPort string) *StatusCmd { + cmd := NewStatusCmd(ctx, "client", "kill", ipPort) + _ = c(ctx, cmd) + return cmd +} + +// ClientKillByFilter is new style syntax, while the ClientKill is old +// +// CLIENT KILL